From ace20176f77624495aa8433ddd63dbbe9a64666f Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 5 Jul 2019 01:01:04 -0400 Subject: [PATCH 01/76] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..cc829ca --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,34 @@ +# ASP.NET Core (.NET Framework) +# Build and test ASP.NET Core projects targeting the full .NET Framework. +# Add steps that publish symbols, save build artifacts, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core + +trigger: +- master + +pool: + vmImage: 'windows-latest' + +variables: + solution: '**/*.sln' + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' + +steps: +- task: NuGetToolInstaller@0 + +- task: NuGetCommand@2 + inputs: + restoreSolution: '$(solution)' + +- task: VSBuild@1 + inputs: + solution: '$(solution)' + msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + +- task: VSTest@2 + inputs: + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' From 39a09985d9c1a5ef4b9c7a0491433a7ffee7641b Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 5 Jul 2019 01:34:18 -0400 Subject: [PATCH 02/76] CI with Azure Pipelines [skip ci] --- azure-pipelines-1.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 azure-pipelines-1.yml diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml new file mode 100644 index 0000000..0ff54be --- /dev/null +++ b/azure-pipelines-1.yml @@ -0,0 +1,17 @@ +# ASP.NET Core +# Build and test ASP.NET Core projects targeting .NET Core. +# Add steps that run tests, create a NuGet package, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core + +trigger: +- dotnet-core + +pool: + vmImage: 'ubuntu-latest' + +variables: + buildConfiguration: 'Release' + +steps: +- script: dotnet build --configuration $(buildConfiguration) + displayName: 'dotnet build $(buildConfiguration)' From 0b7ec2bf8a4a2032b5ae084dbc159f655398d187 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 5 Jul 2019 01:45:33 -0400 Subject: [PATCH 03/76] Update azure-pipelines-1.yml for Azure Pipelines --- azure-pipelines-1.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml index 0ff54be..7ba8a2b 100644 --- a/azure-pipelines-1.yml +++ b/azure-pipelines-1.yml @@ -1,4 +1,3 @@ -# ASP.NET Core # Build and test ASP.NET Core projects targeting .NET Core. # Add steps that run tests, create a NuGet package, deploy, and more: # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core @@ -11,7 +10,14 @@ pool: variables: buildConfiguration: 'Release' + Major: '2' + Minor: '0' + Patch: '0' + Version: '$(Major).$(Minor).$(Patch).$(rev:.r)' steps: -- script: dotnet build --configuration $(buildConfiguration) - displayName: 'dotnet build $(buildConfiguration)' +- task: DotNetCoreCLI@2 + inputs: + command: 'build' + projects: 'src/Ormico.DbPatchManager.sln' + workingDirectory: 'src' \ No newline at end of file From 5eeb02e850f3c6dab5af5304106f330ad55738b5 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 5 Jul 2019 06:33:36 -0400 Subject: [PATCH 04/76] Update azure-pipelines-1.yml for Azure Pipelines --- azure-pipelines-1.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml index 7ba8a2b..04a7ae1 100644 --- a/azure-pipelines-1.yml +++ b/azure-pipelines-1.yml @@ -13,7 +13,7 @@ variables: Major: '2' Minor: '0' Patch: '0' - Version: '$(Major).$(Minor).$(Patch).$(rev:.r)' + Version: '$(Major).$(Minor).$(Patch).$(Build.BuildNumber)' steps: - task: DotNetCoreCLI@2 From 16289f8c8eb06db1c937e8a36979931a4e919b31 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 5 Jul 2019 22:23:40 -0400 Subject: [PATCH 05/76] remove Solution Items folder --- src/Ormico.DbPatchManager.sln | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Ormico.DbPatchManager.sln b/src/Ormico.DbPatchManager.sln index fa1ca88..49bc844 100644 --- a/src/Ormico.DbPatchManager.sln +++ b/src/Ormico.DbPatchManager.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28803.352 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5A4C6A66-8AD5-4E90-A3B5-456ECB6B33E8}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.CLI", "Ormico.DbPatchManager.CLI\Ormico.DbPatchManager.CLI.csproj", "{97E6E000-7E95-4067-BA10-8E9D188C211A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Logic", "Ormico.DbPatchManager.Logic\Ormico.DbPatchManager.Logic.csproj", "{009D6D57-82CD-4617-A0B2-83F70D71E44E}" From a4ec6f5be61f8135ed505174c1f29d083f0aafb0 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 5 Jul 2019 22:31:00 -0400 Subject: [PATCH 06/76] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 7f51c0e..0401bfb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Thomas Moore +Copyright (c) 2019 Zack Moore Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From acda8d5e06281ed0891f0d871a8ebb426253ecc5 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 5 Jul 2019 23:16:15 -0400 Subject: [PATCH 07/76] udpate readme and mark shell scripts to be included in nuget package --- README.md | 11 +++----- .../Ormico.DbPatchManager.CLI.csproj | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9ef880d..7412e4b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,9 @@ # DB Patch Manager Database development tool for change control. - -SQL Server plugin requires SMO v11 SQL2012SP1 x86 -https://www.microsoft.com/en-us/download/details.aspx?id=35580 -Install SQLSysClrTypes.msi and SharedManagementObjects.msi x86 versions. +v2.0 has been updated to .NET Core for cross platform support. ## Create new db project -```PS C:\MyProject> .\dbpatch.exe init --dbtype "Ormico.DbPatchManager.SqlServer.dll, Ormico.DbPatchManager.SqlServer.SqlDatabase"``` +```PS C:\MyProject> .\dbpatch init --dbtype "Ormico.DbPatchManager.SqlServer.dll, Ormico.DbPatchManager.SqlServer.SqlDatabase"``` This will create a new project file named `patches.json` and initilize it to the SQL Server plugin. @@ -47,8 +44,6 @@ The default list of code file extensions and the order they load is: * .trigger3.sql - Trigger ## Build Database -```PS C:\MyProject> .\dbpatch.exe build``` +```PS C:\MyProject> .\dbpatch build``` Applies all missing patches and runs all code files. - -In SQL Server, installed patches are tracked in the a table named InstalledPatches which is added automaticly if it doesn't exist. diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index 0fcb8dd..ba9a141 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -13,9 +13,23 @@ Ormico Ormico DB Patch Manager CLI dbpatch - Ormico.DbPatchManager.Logic + Ormico.DbPatchManager.CLI + + + + + + + + PreserveNewest + + + PreserveNewest + + + @@ -31,13 +45,4 @@ - - - PreserveNewest - - - PreserveNewest - - - From d3f27be4f2088196e3a7199f12c3186d14624c50 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 5 Jul 2019 23:18:02 -0400 Subject: [PATCH 08/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7412e4b..e97afed 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Create a new file named `patches.local.json` When you are a adding files to sour Each developer would enter their local connection string. When deploying, you would enter the production server's connection string. ## Add a database patch -```PS C:\MyProject> .\dbpatch.exe addpatch --name TestPatch``` +```PS C:\MyProject> .\dbpatch addpatch --name TestPatch``` Creates a folder for the patch in `C:\MyProject\Patches\` and adds the patch to the patches.json file. The folder is named using a date time string and a random number and the name. For example something like `201708011412-2403-testpatch`. User can place .sql files in the patch folder and they will be run when the patch is applied. If the user includes more than one patch file, they are run in alphabetical order. From 1dee99a28c2353c76aafc45e4e394aab61a039e0 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sat, 6 Jul 2019 01:03:15 -0400 Subject: [PATCH 09/76] Update azure-pipelines-1.yml for Azure Pipelines --- azure-pipelines-1.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml index 04a7ae1..2c1b34a 100644 --- a/azure-pipelines-1.yml +++ b/azure-pipelines-1.yml @@ -3,7 +3,7 @@ # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core trigger: -- dotnet-core +- master pool: vmImage: 'ubuntu-latest' From 36298b89cc07d2777777dd99260fbbeacec6ad70 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sat, 6 Jul 2019 01:34:16 -0400 Subject: [PATCH 10/76] Update azure-pipelines-1.yml for Azure Pipelines --- azure-pipelines-1.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml index 2c1b34a..83bdc4d 100644 --- a/azure-pipelines-1.yml +++ b/azure-pipelines-1.yml @@ -13,11 +13,19 @@ variables: Major: '2' Minor: '0' Patch: '0' - Version: '$(Major).$(Minor).$(Patch).$(Build.BuildNumber)' + Version: '$(Major).$(Minor).$(Build.BuildNumber)' steps: - task: DotNetCoreCLI@2 inputs: command: 'build' projects: 'src/Ormico.DbPatchManager.sln' - workingDirectory: 'src' \ No newline at end of file + workingDirectory: 'src' +- task: NuGetCommand@2 + inputs: + command: pack + packagesToPack: '**/*.csproj' + versioningScheme: byPrereleaseNumber + majorVersion: '$(Major)' + minorVersion: '$(Minor)' + patchVersion: '$(Build.BuildNumber)' From 7437aeb59df9656ad8f639eff06a8330f21b1d27 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sat, 6 Jul 2019 14:15:58 -0400 Subject: [PATCH 11/76] Update azure-pipelines-1.yml for Azure Pipelines --- azure-pipelines-1.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml index 83bdc4d..6829470 100644 --- a/azure-pipelines-1.yml +++ b/azure-pipelines-1.yml @@ -21,11 +21,8 @@ steps: command: 'build' projects: 'src/Ormico.DbPatchManager.sln' workingDirectory: 'src' -- task: NuGetCommand@2 +- task: DotNetCoreCLI@2 inputs: command: pack - packagesToPack: '**/*.csproj' - versioningScheme: byPrereleaseNumber - majorVersion: '$(Major)' - minorVersion: '$(Minor)' - patchVersion: '$(Build.BuildNumber)' + projects: 'src/Ormico.DbPatchManager.sln' + workingDirectory: 'src' From eec0e80cabe0c51902a90ed14ae26052fbc9e0d7 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 01:47:20 -0400 Subject: [PATCH 12/76] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cc829ca..f3bff5d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,34 +1,14 @@ -# ASP.NET Core (.NET Framework) -# Build and test ASP.NET Core projects targeting the full .NET Framework. -# Add steps that publish symbols, save build artifacts, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core - trigger: - master - pool: - vmImage: 'windows-latest' - + vmImage: 'ubuntu-latest' variables: - solution: '**/*.sln' - buildPlatform: 'Any CPU' buildConfiguration: 'Release' - + Version: '2.0.$(Build.BuildId)' steps: -- task: NuGetToolInstaller@0 - -- task: NuGetCommand@2 +- task: DotNetCoreCLI@2 inputs: - restoreSolution: '$(solution)' - -- task: VSBuild@1 - inputs: - solution: '$(solution)' - msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' - -- task: VSTest@2 - inputs: - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' + command: 'build' + projects: 'src/Ormico.DbPatchManager.sln' + workingDirectory: 'src' + arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' From 0b98f2c152becb0ad866ab1e64920aba833b553c Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 02:03:52 -0400 Subject: [PATCH 13/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f3bff5d..6be3643 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,3 +12,7 @@ steps: projects: 'src/Ormico.DbPatchManager.sln' workingDirectory: 'src' arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: '$(Build.ArtifactStagingDirectory)' + artifactName: nuget-artifacts \ No newline at end of file From dabedba5a71e9235f1eaae59c331954cc4713a96 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 02:34:03 -0400 Subject: [PATCH 14/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6be3643..1b06f47 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,6 +3,7 @@ trigger: pool: vmImage: 'ubuntu-latest' variables: + system.debug: true buildConfiguration: 'Release' Version: '2.0.$(Build.BuildId)' steps: From 7760c02971e56e2ce59728a33b3105605ede1410 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 02:41:13 -0400 Subject: [PATCH 15/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1b06f47..739282b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,6 +7,7 @@ variables: buildConfiguration: 'Release' Version: '2.0.$(Build.BuildId)' steps: +- bash: echo $(Version) - task: DotNetCoreCLI@2 inputs: command: 'build' From 5c7a942f77b963a293deb78f9bf3971f0c683bee Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 02:45:36 -0400 Subject: [PATCH 16/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 739282b..aec1822 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,7 +7,14 @@ variables: buildConfiguration: 'Release' Version: '2.0.$(Build.BuildId)' steps: -- bash: echo $(Version) +- task: Bash@3 + inputs: + targetType: 'inline' + script: '# Write your commands here + echo xxxxxxxxxxxxxxxxxx + echo $(Version) + echo xxxxxxxxxxxxxxxxxx + # Use the environment variables input below to pass secret variables to this script' - task: DotNetCoreCLI@2 inputs: command: 'build' From a0c7860490bdcaa0393e78084c4d480a897c6a7f Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 02:51:19 -0400 Subject: [PATCH 17/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index aec1822..47cdcf1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,14 +7,7 @@ variables: buildConfiguration: 'Release' Version: '2.0.$(Build.BuildId)' steps: -- task: Bash@3 - inputs: - targetType: 'inline' - script: '# Write your commands here - echo xxxxxxxxxxxxxxxxxx - echo $(Version) - echo xxxxxxxxxxxxxxxxxx - # Use the environment variables input below to pass secret variables to this script' +- bash: echo v$(Version) - task: DotNetCoreCLI@2 inputs: command: 'build' From 384a8f9155b4ed085b3b6cf1b12756c07fd6f08c Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 03:20:37 -0400 Subject: [PATCH 18/76] update version setting --- .../Ormico.DbPatchManager.CLI.csproj | 2 +- .../Properties/PublishProfiles/FolderProfile.pubxml | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index ba9a141..d480061 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -5,7 +5,7 @@ netcoreapp2.2 true true - 2.0.0.1 + 2.0.0 LICENSE https://github.com/ormico/dbpatchmanager Copyright (c) 2019 Zack Moore diff --git a/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml b/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..acc147b --- /dev/null +++ b/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,13 @@ + + + + + FileSystem + Release + Any CPU + netcoreapp2.2 + bin\Release\netcoreapp2.2\publish\ + + \ No newline at end of file From 6883cbb8217f77cf27663d61eb49d16432cbef83 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 03:24:01 -0400 Subject: [PATCH 19/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 47cdcf1..9ef143a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,7 +11,7 @@ steps: - task: DotNetCoreCLI@2 inputs: command: 'build' - projects: 'src/Ormico.DbPatchManager.sln' +# projects: 'src/Ormico.DbPatchManager.sln' workingDirectory: 'src' arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 From 64136849535469c99defa989a15c4bd6cb3e3b28 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 03:26:35 -0400 Subject: [PATCH 20/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9ef143a..7d01770 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,7 +5,7 @@ pool: variables: system.debug: true buildConfiguration: 'Release' - Version: '2.0.$(Build.BuildId)' + Version: '2.0.0.$(Build.BuildId)' steps: - bash: echo v$(Version) - task: DotNetCoreCLI@2 From 4a25cbb35391ef84b6f75c801d7d4e50fba4f52f Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 03:27:04 -0400 Subject: [PATCH 21/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7d01770..3c603d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,7 +5,7 @@ pool: variables: system.debug: true buildConfiguration: 'Release' - Version: '2.0.0.$(Build.BuildId)' + Version: '2.0.0.$(Build.BuildID)' steps: - bash: echo v$(Version) - task: DotNetCoreCLI@2 From f7134c276fc703bc61591885c9fa5c72a25b4a2d Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 04:14:40 -0400 Subject: [PATCH 22/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3c603d8..b84d199 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,7 +13,7 @@ steps: command: 'build' # projects: 'src/Ormico.DbPatchManager.sln' workingDirectory: 'src' - arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' +# arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' From 0d2b2ec258f0de2307bf3608b8c49bb94b471a32 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 04:16:24 -0400 Subject: [PATCH 23/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b84d199..5864e2e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,8 +12,9 @@ steps: inputs: command: 'build' # projects: 'src/Ormico.DbPatchManager.sln' + projects: 'src/*.sln' workingDirectory: 'src' -# arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' + arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' From 2ce519ce60ba9cb5d28d8b38fd625eb160514e22 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 04:58:18 -0400 Subject: [PATCH 24/76] add rests --- .../Ormico.DbPatchManager.Logic.Tests.csproj | 19 +++++++++++++++++++ .../UnitTest1.cs | 13 +++++++++++++ src/Ormico.DbPatchManager.sln | 6 ++++++ 3 files changed, 38 insertions(+) create mode 100644 src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj create mode 100644 src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs diff --git a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj new file mode 100644 index 0000000..f3e5e8e --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + diff --git a/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs b/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs new file mode 100644 index 0000000..0008e77 --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs @@ -0,0 +1,13 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Ormico.DbPatchManager.Logic.Tests +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/src/Ormico.DbPatchManager.sln b/src/Ormico.DbPatchManager.sln index 49bc844..82396be 100644 --- a/src/Ormico.DbPatchManager.sln +++ b/src/Ormico.DbPatchManager.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.CLI", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Logic", "Ormico.DbPatchManager.Logic\Ormico.DbPatchManager.Logic.csproj", "{009D6D57-82CD-4617-A0B2-83F70D71E44E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ormico.DbPatchManager.Logic.Tests", "Ormico.DbPatchManager.Logic.Tests\Ormico.DbPatchManager.Logic.Tests.csproj", "{59FD6996-4792-4F29-8F31-5873BC61F974}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {009D6D57-82CD-4617-A0B2-83F70D71E44E}.Debug|Any CPU.Build.0 = Debug|Any CPU {009D6D57-82CD-4617-A0B2-83F70D71E44E}.Release|Any CPU.ActiveCfg = Release|Any CPU {009D6D57-82CD-4617-A0B2-83F70D71E44E}.Release|Any CPU.Build.0 = Release|Any CPU + {59FD6996-4792-4F29-8F31-5873BC61F974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59FD6996-4792-4F29-8F31-5873BC61F974}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59FD6996-4792-4F29-8F31-5873BC61F974}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59FD6996-4792-4F29-8F31-5873BC61F974}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 6cca69dbba78625135febe5da1d0b5d96263200a Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 05:12:36 -0400 Subject: [PATCH 25/76] remove version --- src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj | 1 - .../Ormico.DbPatchManager.Logic.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index d480061..4d188e2 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -5,7 +5,6 @@ netcoreapp2.2 true true - 2.0.0 LICENSE https://github.com/ormico/dbpatchmanager Copyright (c) 2019 Zack Moore diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index 15ef0ce..825363d 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -2,7 +2,6 @@ netstandard2.0 - 2.0.0 true true Copyright (c) 2019 Zack Moore From 21b0fbc91b0a7e019f39fd9ec6cdd11c3eebd525 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Sun, 7 Jul 2019 05:17:16 -0400 Subject: [PATCH 26/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5864e2e..d63d586 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,8 +11,7 @@ steps: - task: DotNetCoreCLI@2 inputs: command: 'build' -# projects: 'src/Ormico.DbPatchManager.sln' - projects: 'src/*.sln' + projects: 'src/Ormico.DbPatchManager.sln' workingDirectory: 'src' arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 From 0199940ff7b61b4c69be9b34927da0c13ad37784 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 21:20:37 -0400 Subject: [PATCH 27/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d63d586..8dba1d0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,6 +14,12 @@ steps: projects: 'src/Ormico.DbPatchManager.sln' workingDirectory: 'src' arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' +- task: DotNetCoreCLI@2 + inputs: + command: 'publish' + projects: 'src/Ormico.DbPatchManager.sln' + workingDirectory: 'src' + arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' From b7e143d85c635c0824636cecb47c6cf928549323 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 21:39:26 -0400 Subject: [PATCH 28/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8dba1d0..b2451ba 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,4 +23,8 @@ steps: - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: nuget-artifacts \ No newline at end of file + artifactName: nuget-artifacts +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: 'src/Ormico.DbPatchManager.CLI/bin/Release/netcoreapp2.2/publish/' + artifactName: zip-artifacts From 83570b996d2607cc2ba708a12f2dd41cd2d3166a Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 21:45:18 -0400 Subject: [PATCH 29/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b2451ba..22e1cc9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,7 +19,7 @@ steps: command: 'publish' projects: 'src/Ormico.DbPatchManager.sln' workingDirectory: 'src' - arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' + arguments: '--configuration $(buildConfiguration) - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' From 49032cb7ce946f2a232ca21c346e3aa9d4fa111d Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 21:46:29 -0400 Subject: [PATCH 30/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 22e1cc9..fdf99d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,7 +17,7 @@ steps: - task: DotNetCoreCLI@2 inputs: command: 'publish' - projects: 'src/Ormico.DbPatchManager.sln' + projects: 'src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj' workingDirectory: 'src' arguments: '--configuration $(buildConfiguration) - task: PublishBuildArtifacts@1 From d7c881094f52259620b622d585ecd60490a8c869 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 21:47:28 -0400 Subject: [PATCH 31/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fdf99d8..e8e4d39 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,7 +19,7 @@ steps: command: 'publish' projects: 'src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj' workingDirectory: 'src' - arguments: '--configuration $(buildConfiguration) + arguments: '--configuration $(buildConfiguration)' - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' From 30775329dea7aec46c8bc4d25cb6abe423845266 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 21:50:44 -0400 Subject: [PATCH 32/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e8e4d39..ca748f3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,9 +17,8 @@ steps: - task: DotNetCoreCLI@2 inputs: command: 'publish' - projects: 'src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj' - workingDirectory: 'src' - arguments: '--configuration $(buildConfiguration)' + publishWebProjects: true + workingDirectory: 'src/Ormico.DbPatchManager.CLI/' - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' From 6dc5a45ac7b71de857d986fca87c7c4052e5347e Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 21:53:20 -0400 Subject: [PATCH 33/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ca748f3..5ad9e1c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,11 +14,7 @@ steps: projects: 'src/Ormico.DbPatchManager.sln' workingDirectory: 'src' arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' -- task: DotNetCoreCLI@2 - inputs: - command: 'publish' - publishWebProjects: true - workingDirectory: 'src/Ormico.DbPatchManager.CLI/' +- bash: dotnet publish src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' From 8cc70fd0c26d4078b115126fda91efd312445b6f Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 21:55:24 -0400 Subject: [PATCH 34/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5ad9e1c..91c122d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,7 +12,6 @@ steps: inputs: command: 'build' projects: 'src/Ormico.DbPatchManager.sln' - workingDirectory: 'src' arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' - bash: dotnet publish src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj - task: PublishBuildArtifacts@1 From 85fa348833edb95dfbfaea7d0eaa00a32ac58528 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 22:01:13 -0400 Subject: [PATCH 35/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 91c122d..7c3b78a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,7 +13,7 @@ steps: command: 'build' projects: 'src/Ormico.DbPatchManager.sln' arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' -- bash: dotnet publish src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +- bash: dotnet publish -c release src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' From 33ea08e1ca5e17b0e54192045c2ed32903d8e3ea Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 22:04:09 -0400 Subject: [PATCH 36/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7c3b78a..1895f1d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,6 +13,7 @@ steps: command: 'build' projects: 'src/Ormico.DbPatchManager.sln' arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' +- bash: dotnet build -c release src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj - bash: dotnet publish -c release src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj - task: PublishBuildArtifacts@1 inputs: From 9650062831cd3e1c49a1ceb0f6f007ed1e75b862 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Thu, 25 Jul 2019 22:13:11 -0400 Subject: [PATCH 37/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1895f1d..57984e6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,5 +21,5 @@ steps: artifactName: nuget-artifacts - task: PublishBuildArtifacts@1 inputs: - pathtoPublish: 'src/Ormico.DbPatchManager.CLI/bin/Release/netcoreapp2.2/publish/' + pathtoPublish: 'src/Ormico.DbPatchManager.CLI/bin/release/netcoreapp2.2/publish/' artifactName: zip-artifacts From fcf24d63866532a02df1725c1c81cfda438cd03b Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 26 Jul 2019 11:51:32 -0400 Subject: [PATCH 38/76] truncate long patch finalId --- src/Ormico.DbPatchManager.Logic/PatchManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Ormico.DbPatchManager.Logic/PatchManager.cs b/src/Ormico.DbPatchManager.Logic/PatchManager.cs index ba5e6d7..d2b37fd 100644 --- a/src/Ormico.DbPatchManager.Logic/PatchManager.cs +++ b/src/Ormico.DbPatchManager.Logic/PatchManager.cs @@ -90,7 +90,9 @@ public void AddPatch(string patchName, PatchOptions Options = null) string prefix = string.Format("{0:yyyyMMddHHmm}-{1:0000}", DateTime.Now, _rand.Next(0, 9999)); - string finalId = string.Format("{0}-{1}", prefix, patchName.Trim()); + + // patch names are limited to 50 char total at present, but this could be a property of the plugin + string finalId = $"{prefix}-{patchName.Trim()}".Substring(0, 50); string patchPath = _io.Path.Combine(cfg.PatchFolder, finalId); if(!_io.Directory.Exists(patchPath)) @@ -107,7 +109,7 @@ public void AddPatch(string patchName, PatchOptions Options = null) else { // create custom exception - throw new ApplicationException(string.Format("A folder named '{0}' already exists", finalId)); + throw new ApplicationException($"A folder named '{finalId}' already exists"); } } } From 91fae4b57fe94b329ed7e6adc2c4a0448ec6f557 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Fri, 26 Jul 2019 12:28:28 -0400 Subject: [PATCH 39/76] Update PatchManager.cs --- src/Ormico.DbPatchManager.Logic/PatchManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Ormico.DbPatchManager.Logic/PatchManager.cs b/src/Ormico.DbPatchManager.Logic/PatchManager.cs index d2b37fd..d74cb60 100644 --- a/src/Ormico.DbPatchManager.Logic/PatchManager.cs +++ b/src/Ormico.DbPatchManager.Logic/PatchManager.cs @@ -87,12 +87,12 @@ public void AddPatch(string patchName, PatchOptions Options = null) //DatabaseOptions dbopt = LoadDatabaseOptions(cfg); //create unique id prefix to avoid collisions - string prefix = string.Format("{0:yyyyMMddHHmm}-{1:0000}", - DateTime.Now, - _rand.Next(0, 9999)); + string prefix = $"{DateTime.Now:yyyyMMddHHmm}-{_rand.Next(0, 9999):0000}"; // patch names are limited to 50 char total at present, but this could be a property of the plugin - string finalId = $"{prefix}-{patchName.Trim()}".Substring(0, 50); + string finalId = $"{prefix}-{patchName.Trim()}"; + finalId = finalId.Substring(0, Math.Min(50, finalId.Length)); + string patchPath = _io.Path.Combine(cfg.PatchFolder, finalId); if(!_io.Directory.Exists(patchPath)) From 7d2aa7d73523278d8d21ce80559921715257520c Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Tue, 27 Aug 2019 15:32:23 -0400 Subject: [PATCH 40/76] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e97afed..208e0bb 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,6 @@ The default list of code file extensions and the order they load is: ```PS C:\MyProject> .\dbpatch build``` Applies all missing patches and runs all code files. + +## Database Plugin +[SQL Server Plugin](https://github.com/ormico/dbpatchmanager-sqlserver) From 5e7d0e50ac72da78dec375dc57092e678aff1a14 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Mon, 13 Jan 2020 18:00:01 -0500 Subject: [PATCH 41/76] sort patch files before installing --- .../PatchManager.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ormico.DbPatchManager.Logic/PatchManager.cs b/src/Ormico.DbPatchManager.Logic/PatchManager.cs index d74cb60..b723aaf 100644 --- a/src/Ormico.DbPatchManager.Logic/PatchManager.cs +++ b/src/Ormico.DbPatchManager.Logic/PatchManager.cs @@ -181,14 +181,13 @@ private void InstallPatch(Patch patch, IDatabase db, List in { LinkedList graph = new LinkedList(); graph.AddLast(patch); - bool isInstalled; Patch current; - while (graph.Count() > 0) + while (graph.Any()) { current = graph.First.Value; graph.RemoveFirst(); - isInstalled = installedPatches.Any(i => string.Equals(i.PatchId, current.Id)); + var isInstalled = installedPatches.Any(i => string.Equals(i.PatchId, current.Id)); List notInstalledDependencies = new List(); // make sure all dependencies are installed @@ -196,10 +195,9 @@ private void InstallPatch(Patch patch, IDatabase db, List in { // check each dependency and see if it is already installed // add not installed dependencies to a list - bool isDepInstalled; foreach (Patch dependency in current.DependsOn) { - isDepInstalled = installedPatches.Any(i => string.Equals(i.PatchId, dependency.Id)); + var isDepInstalled = installedPatches.Any(i => string.Equals(i.PatchId, dependency.Id)); if(!isDepInstalled) { notInstalledDependencies.Add(dependency); @@ -207,7 +205,7 @@ private void InstallPatch(Patch patch, IDatabase db, List in } } - if(notInstalledDependencies.Count() > 0) + if(notInstalledDependencies.Any()) { // if there are dependencies to install // put current back on stack and put dependencies on stack @@ -225,20 +223,22 @@ private void InstallPatch(Patch patch, IDatabase db, List in // if there are no dependencies and patch is not installed then install it if (!_io.Directory.Exists(folder)) { - throw new ApplicationException(string.Format("Patch folder '{0}' missing.", current.Id)); + throw new ApplicationException($"Patch folder '{current.Id}' missing."); } else { // make sure patch isn't already installed if (isInstalled) { - Console.WriteLine("{0} ", current.Id); + //Console.WriteLine("{0} ", current.Id); + Console.WriteLine(">"); } else { + Console.WriteLine(); Console.WriteLine(current.Id); - var files = _io.Directory.GetFiles(folder); + var files = _io.Directory.GetFiles(folder).OrderBy(s => s); foreach (var file in files) { Console.WriteLine(file); From 2a05df69b27c47aacdf2e5478807f1c0da600760 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Mon, 13 Jan 2020 18:23:56 -0500 Subject: [PATCH 42/76] don't relog the IDs of patches already installed over and over --- src/Ormico.DbPatchManager.Logic/PatchManager.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Ormico.DbPatchManager.Logic/PatchManager.cs b/src/Ormico.DbPatchManager.Logic/PatchManager.cs index b723aaf..797de86 100644 --- a/src/Ormico.DbPatchManager.Logic/PatchManager.cs +++ b/src/Ormico.DbPatchManager.Logic/PatchManager.cs @@ -182,6 +182,7 @@ private void InstallPatch(Patch patch, IDatabase db, List in LinkedList graph = new LinkedList(); graph.AddLast(patch); Patch current; + List idsLogged = new List(); while (graph.Any()) { @@ -230,8 +231,16 @@ private void InstallPatch(Patch patch, IDatabase db, List in // make sure patch isn't already installed if (isInstalled) { - //Console.WriteLine("{0} ", current.Id); - Console.WriteLine(">"); + if (idsLogged.Contains(current.Id)) + { + Console.Write(">"); + } + else + { + Console.WriteLine(); + Console.Write("{0}: already installed>", current.Id); + idsLogged.Add(current.Id); + } } else { @@ -267,6 +276,7 @@ private void InstallPatch(Patch patch, IDatabase db, List in } } } + Console.WriteLine(); } /// From 252227f9077a2470653d896764b14a6fb3dddbc8 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Mon, 13 Jan 2020 19:52:30 -0500 Subject: [PATCH 43/76] mod path to be linux compatable --- src/Ormico.DbPatchManager.CLI/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ormico.DbPatchManager.CLI/Program.cs b/src/Ormico.DbPatchManager.CLI/Program.cs index 2fa81b8..af33596 100644 --- a/src/Ormico.DbPatchManager.CLI/Program.cs +++ b/src/Ormico.DbPatchManager.CLI/Program.cs @@ -33,8 +33,8 @@ static int Main(string[] args) return rc; } - private const string _patchFileName = ".\\patches.json"; - private const string _patchLocalFileName = ".\\patches.local.json"; + private const string _patchFileName = "patches.json"; + private const string _patchLocalFileName = "patches.local.json"; public static bool StrEq(string a, string b) { From bef6410a0cf374479f686d7d927ee00b3277ced1 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Tue, 14 Jan 2020 10:19:40 -0500 Subject: [PATCH 44/76] update intro in README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 208e0bb..e0d8601 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Database development tool for change control. v2.0 has been updated to .NET Core for cross platform support. +DB Patch Manager supports database development across multiple source control branches by tracking the dependencies of each patch and installing them in the correct order. When a developer adds a new patch, that patch depends on all the previous patches recorded in that branch. When branches are merged the `patches.json` file is merged to include all patches and the dependency tree from each branch. + +Database changes are deployed using the `build` command. DB Patch Manager will check the database to determine which patches have already been installed before installing new patches in the correct order. + ## Create new db project ```PS C:\MyProject> .\dbpatch init --dbtype "Ormico.DbPatchManager.SqlServer.dll, Ormico.DbPatchManager.SqlServer.SqlDatabase"``` From 10a96beb467337e45f4d8eeda46c751e1af69cd3 Mon Sep 17 00:00:00 2001 From: Zack Moore Date: Tue, 10 Mar 2020 15:58:28 -0400 Subject: [PATCH 45/76] spinner; fix duplicate handling2 --- .../AddPatchCmdLineOptions.cs | 2 +- .../Ormico.DbPatchManager.CLI.csproj | 5 +-- .../Properties/launchSettings.json | 8 +++++ .../BuildConfigurationWriter.cs | 6 ++-- .../Ormico.DbPatchManager.Logic.csproj | 5 +-- src/Ormico.DbPatchManager.Logic/Patch.cs | 35 ++++++++++++++++--- .../PatchManager.cs | 9 +++-- src/Ormico.DbPatchManager.Logic/ProgressUi.cs | 34 ++++++++++++++++++ 8 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 src/Ormico.DbPatchManager.CLI/Properties/launchSettings.json create mode 100644 src/Ormico.DbPatchManager.Logic/ProgressUi.cs diff --git a/src/Ormico.DbPatchManager.CLI/CommandLineOptions/AddPatchCmdLineOptions.cs b/src/Ormico.DbPatchManager.CLI/CommandLineOptions/AddPatchCmdLineOptions.cs index b363588..c159931 100644 --- a/src/Ormico.DbPatchManager.CLI/CommandLineOptions/AddPatchCmdLineOptions.cs +++ b/src/Ormico.DbPatchManager.CLI/CommandLineOptions/AddPatchCmdLineOptions.cs @@ -2,7 +2,7 @@ namespace Ormico.DbPatchManager.CLI.CommandLineOptions { - [Verb("addpatch", HelpText = "Create a new Patch folder and add it to odpm.json")] + [Verb("addpatch", HelpText = "Create a new Patch folder and add it to patches.json")] class AddPatchCmdLineOptions { public AddPatchCmdLineOptions() diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index 4d188e2..13746a7 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,12 +7,13 @@ true LICENSE https://github.com/ormico/dbpatchmanager - Copyright (c) 2019 Zack Moore + Copyright (c) 2020 Zack Moore Zack Moore Ormico Ormico DB Patch Manager CLI dbpatch Ormico.DbPatchManager.CLI + https://dbpatch.dev diff --git a/src/Ormico.DbPatchManager.CLI/Properties/launchSettings.json b/src/Ormico.DbPatchManager.CLI/Properties/launchSettings.json new file mode 100644 index 0000000..1bc138b --- /dev/null +++ b/src/Ormico.DbPatchManager.CLI/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Ormico.DbPatchManager.CLI": { + "commandName": "Project", + "commandLineArgs": "build" + } + } +} \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs b/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs index 7b9aa5c..dba9bb5 100644 --- a/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs +++ b/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs @@ -91,14 +91,14 @@ from a in patches where (string)x["id"] == p.Id && a.Id == (string)d select a; - p.DependsOn = cur.ToList(); + p.DependsOn = cur.Distinct(new PatchComparer()).ToList(); //todo: double check this query var children = from x in o["patches"] from d in x["dependsOn"] from a in patches where (string)d == p.Id && (string)x["id"] == a.Id select a; - p.Children = children.ToList(); + p.Children = children.Distinct(new PatchComparer()).ToList(); } rc.patches = patches.ToList(); } @@ -177,7 +177,7 @@ public void Write(DatabaseBuildConfiguration buildConfiguration) select new { id = p.Id, - dependsOn = p.DependsOn != null?(from d in p.DependsOn + dependsOn = p.DependsOn != null?(from d in p.DependsOn.Distinct(new PatchComparer()) select d.Id):null } }); diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index 825363d..77efa52 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -1,15 +1,16 @@ - + netstandard2.0 true true - Copyright (c) 2019 Zack Moore + Copyright (c) 2020 Zack Moore LICENSE Ormico Zack Moore https://github.com/ormico/dbpatchmanager Ormico DB Patch Manager Logic + https://dbpatch.dev diff --git a/src/Ormico.DbPatchManager.Logic/Patch.cs b/src/Ormico.DbPatchManager.Logic/Patch.cs index 3653023..03cc0c7 100644 --- a/src/Ormico.DbPatchManager.Logic/Patch.cs +++ b/src/Ormico.DbPatchManager.Logic/Patch.cs @@ -19,13 +19,40 @@ public Patch(string Id, List DependsOn) //todo: constructor for json.net - [JsonProperty("id")] - public string Id { get; /* protected */ set; } + [JsonProperty("id")] public string Id { get; /* protected */ set; } [JsonProperty("dependsOn", ItemIsReference = true)] public List DependsOn { get; /* protected */ set; } - [JsonIgnore] - public List Children { get; /* protected */ set; } + [JsonIgnore] public List Children { get; /* protected */ set; } + } + + public class PatchComparer : IEqualityComparer + { + // Products are equal if their id is equal. + public bool Equals(Patch x, Patch y) + { + //Check whether the compared objects reference the same data. + if (Object.ReferenceEquals(x, y)) return true; + + //Check whether any of the compared objects is null. + if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) + return false; + + //Check whether the products' properties are equal. + return string.Equals(x.Id, y.Id); + } + + // If Equals() returns true for a pair of objects + // then GetHashCode() must return the same value for these objects. + + public int GetHashCode(Patch product) + { + //Get hash code for the Name field if it is not null. + int rc = product?.Id?.GetHashCode()??0; + + //Calculate the hash code for the product. + return rc; + } } } \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic/PatchManager.cs b/src/Ormico.DbPatchManager.Logic/PatchManager.cs index 797de86..d38d137 100644 --- a/src/Ormico.DbPatchManager.Logic/PatchManager.cs +++ b/src/Ormico.DbPatchManager.Logic/PatchManager.cs @@ -233,12 +233,12 @@ private void InstallPatch(Patch patch, IDatabase db, List in { if (idsLogged.Contains(current.Id)) { - Console.Write(">"); + ProgressUi.PrintProgress(); } else { Console.WriteLine(); - Console.Write("{0}: already installed>", current.Id); + Console.Write("{0}: already installed ", current.Id); idsLogged.Add(current.Id); } } @@ -271,7 +271,10 @@ private void InstallPatch(Patch patch, IDatabase db, List in // add children of current foreach (var c in current.Children) { - graph.AddLast(c); + if (graph.Any(i => string.Equals(i.Id, c.Id)) == false) + { + graph.AddLast(c); + } } } } diff --git a/src/Ormico.DbPatchManager.Logic/ProgressUi.cs b/src/Ormico.DbPatchManager.Logic/ProgressUi.cs new file mode 100644 index 0000000..b08a039 --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic/ProgressUi.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ormico.DbPatchManager.Logic +{ + class ProgressUi + { + private static readonly char[] spinner = { '-', '\\', '|', '/' }; + private static int spinnerPosition = -1; + + public static void PrintProgress() + { + spinnerPosition++; + if (spinnerPosition < 0 || spinnerPosition > spinner.Length - 1) + { + spinnerPosition = 0; + } + + if (Console.CursorLeft > 0) + { + try + { + Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); + } + catch (Exception e) + { + // in case console position cannot be set + } + } + Console.Write(spinner[spinnerPosition]); + } + } +} From 001bb78e5cfecd980a4a491a629c6ea320868dac Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Sat, 27 Jun 2020 10:19:04 -0400 Subject: [PATCH 46/76] code-scanning --- .github/workflows/codeql-analysis.yml | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..7f188ab --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,51 @@ +name: "Code scanning - action" + +on: + push: + pull_request: + schedule: + - cron: '0 11 * * 5' + +jobs: + CodeQL-Build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 2e3345e8d8a0d99d84f2677d53bb843c71b0e58a Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 29 Jun 2020 23:34:59 -0400 Subject: [PATCH 47/76] add icon file --- assets/dbpatch-manager-profile.png | Bin 0 -> 16412 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/dbpatch-manager-profile.png diff --git a/assets/dbpatch-manager-profile.png b/assets/dbpatch-manager-profile.png new file mode 100644 index 0000000000000000000000000000000000000000..c99b25ed37cb6c62b76ab4e46299adf39be8d8c3 GIT binary patch literal 16412 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 literal 0 HcmV?d00001 From ddef66f16d200be46df750643c2dcfb2100a0efd Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 29 Jun 2020 23:35:16 -0400 Subject: [PATCH 48/76] update license copyright year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0401bfb..37c6207 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Zack Moore +Copyright (c) 2020 Zack Moore Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 812a5925644d4937dafaac74036ad5f1571c101d Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 29 Jun 2020 23:38:52 -0400 Subject: [PATCH 49/76] split out common lib; update to dotnet core 3.1 --- .../Ormico.DbPatchManager.CLI.csproj | 13 +++++++-- .../PublishProfiles/FolderProfile.pubxml | 5 ++-- .../DatabaseOptions.cs | 2 +- .../IDatabase.cs | 2 +- .../InstalledPatchInfo.cs | 2 +- .../Ormico.DbPatchManager.Common.csproj | 29 +++++++++++++++++++ .../OdbcDatabase.cs | 1 + .../Ormico.DbPatchManager.Logic.csproj | 17 +++++++++-- .../PatchManager.cs | 3 +- .../PluginManager.cs | 2 +- .../TestDatabase.cs | 1 + src/Ormico.DbPatchManager.sln | 8 ++++- 12 files changed, 71 insertions(+), 14 deletions(-) rename src/{Ormico.DbPatchManager.Logic => Ormico.DbPatchManager.Common}/DatabaseOptions.cs (93%) rename src/{Ormico.DbPatchManager.Logic => Ormico.DbPatchManager.Common}/IDatabase.cs (91%) rename src/{Ormico.DbPatchManager.Logic => Ormico.DbPatchManager.Common}/InstalledPatchInfo.cs (87%) create mode 100644 src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index 13746a7..89555d5 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -1,8 +1,8 @@ - + Exe - netcoreapp2.2 + netcoreapp3.1 true true LICENSE @@ -13,12 +13,19 @@ Ormico DB Patch Manager CLI dbpatch Ormico.DbPatchManager.CLI - https://dbpatch.dev + https://dbpatch.dev/ + 2.1.0 + dbpatch-manager-profile.png + Command Line Interface for Database Change managment designed for multi-dev/multi-branch. + + True + + diff --git a/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml b/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml index acc147b..47d90a6 100644 --- a/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml @@ -7,7 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release Any CPU - netcoreapp2.2 - bin\Release\netcoreapp2.2\publish\ + netcoreapp3.1 + bin\Release\netcoreapp3.1\publish\ + false \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs b/src/Ormico.DbPatchManager.Common/DatabaseOptions.cs similarity index 93% rename from src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs rename to src/Ormico.DbPatchManager.Common/DatabaseOptions.cs index 417fb2f..89cb4ed 100644 --- a/src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs +++ b/src/Ormico.DbPatchManager.Common/DatabaseOptions.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Ormico.DbPatchManager.Logic +namespace Ormico.DbPatchManager.Common { public struct DatabaseOptions { diff --git a/src/Ormico.DbPatchManager.Logic/IDatabase.cs b/src/Ormico.DbPatchManager.Common/IDatabase.cs similarity index 91% rename from src/Ormico.DbPatchManager.Logic/IDatabase.cs rename to src/Ormico.DbPatchManager.Common/IDatabase.cs index 7ffb5aa..0da346c 100644 --- a/src/Ormico.DbPatchManager.Logic/IDatabase.cs +++ b/src/Ormico.DbPatchManager.Common/IDatabase.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Ormico.DbPatchManager.Logic +namespace Ormico.DbPatchManager.Common { public interface IDatabase: IDisposable { diff --git a/src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs b/src/Ormico.DbPatchManager.Common/InstalledPatchInfo.cs similarity index 87% rename from src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs rename to src/Ormico.DbPatchManager.Common/InstalledPatchInfo.cs index 4c4a073..c6cc358 100644 --- a/src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs +++ b/src/Ormico.DbPatchManager.Common/InstalledPatchInfo.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Ormico.DbPatchManager.Logic +namespace Ormico.DbPatchManager.Common { public class InstalledPatchInfo { diff --git a/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj b/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj new file mode 100644 index 0000000..07f80eb --- /dev/null +++ b/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj @@ -0,0 +1,29 @@ + + + + netstandard2.0 + https://github.com/ormico/dbpatchmanager + LICENSE + Zack Moore + Ormico + true + Ormico DB Patch Manager Common + dbpatch-manager-profile.png + true + Classes and interfaces used to implement a DB Patch Manager Database Module. + Copyright (c) 2020 Zack Moore + https://dbpatch.dev/ + + + + + True + + + + True + + + + + diff --git a/src/Ormico.DbPatchManager.Logic/OdbcDatabase.cs b/src/Ormico.DbPatchManager.Logic/OdbcDatabase.cs index defe0f9..eeb6464 100644 --- a/src/Ormico.DbPatchManager.Logic/OdbcDatabase.cs +++ b/src/Ormico.DbPatchManager.Logic/OdbcDatabase.cs @@ -1,4 +1,5 @@ using Dapper; +using Ormico.DbPatchManager.Common; using System; using System.Collections.Generic; using System.Data.Odbc; diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index 77efa52..d20aead 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -1,8 +1,8 @@ - + netstandard2.0 - true + false true Copyright (c) 2020 Zack Moore LICENSE @@ -10,7 +10,10 @@ Zack Moore https://github.com/ormico/dbpatchmanager Ormico DB Patch Manager Logic - https://dbpatch.dev + https://dbpatch.dev/ + 2.1.0 + dbpatch-manager-profile.png + Logic Library for Database Change managment designed for multi-dev/multi-branch. @@ -21,10 +24,18 @@ + + True + + True + + + + diff --git a/src/Ormico.DbPatchManager.Logic/PatchManager.cs b/src/Ormico.DbPatchManager.Logic/PatchManager.cs index d38d137..783c39b 100644 --- a/src/Ormico.DbPatchManager.Logic/PatchManager.cs +++ b/src/Ormico.DbPatchManager.Logic/PatchManager.cs @@ -1,4 +1,5 @@ -using System; +using Ormico.DbPatchManager.Common; +using System; using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; diff --git a/src/Ormico.DbPatchManager.Logic/PluginManager.cs b/src/Ormico.DbPatchManager.Logic/PluginManager.cs index 444e855..73ca8e0 100644 --- a/src/Ormico.DbPatchManager.Logic/PluginManager.cs +++ b/src/Ormico.DbPatchManager.Logic/PluginManager.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; using System.Reflection; - +using Ormico.DbPatchManager.Common; namespace Ormico.DbPatchManager.Logic { diff --git a/src/Ormico.DbPatchManager.Logic/TestDatabase.cs b/src/Ormico.DbPatchManager.Logic/TestDatabase.cs index 0cc1c1d..3edd792 100644 --- a/src/Ormico.DbPatchManager.Logic/TestDatabase.cs +++ b/src/Ormico.DbPatchManager.Logic/TestDatabase.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using Ormico.DbPatchManager.Common; using System; using System.Collections.Generic; using System.Data.Common; diff --git a/src/Ormico.DbPatchManager.sln b/src/Ormico.DbPatchManager.sln index 82396be..37c0270 100644 --- a/src/Ormico.DbPatchManager.sln +++ b/src/Ormico.DbPatchManager.sln @@ -7,7 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.CLI", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Logic", "Ormico.DbPatchManager.Logic\Ormico.DbPatchManager.Logic.csproj", "{009D6D57-82CD-4617-A0B2-83F70D71E44E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ormico.DbPatchManager.Logic.Tests", "Ormico.DbPatchManager.Logic.Tests\Ormico.DbPatchManager.Logic.Tests.csproj", "{59FD6996-4792-4F29-8F31-5873BC61F974}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Logic.Tests", "Ormico.DbPatchManager.Logic.Tests\Ormico.DbPatchManager.Logic.Tests.csproj", "{59FD6996-4792-4F29-8F31-5873BC61F974}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ormico.DbPatchManager.Common", "Ormico.DbPatchManager.Common\Ormico.DbPatchManager.Common.csproj", "{EB7E7876-55E8-4B9D-82BA-387B90F363F6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,6 +29,10 @@ Global {59FD6996-4792-4F29-8F31-5873BC61F974}.Debug|Any CPU.Build.0 = Debug|Any CPU {59FD6996-4792-4F29-8F31-5873BC61F974}.Release|Any CPU.ActiveCfg = Release|Any CPU {59FD6996-4792-4F29-8F31-5873BC61F974}.Release|Any CPU.Build.0 = Release|Any CPU + {EB7E7876-55E8-4B9D-82BA-387B90F363F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB7E7876-55E8-4B9D-82BA-387B90F363F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB7E7876-55E8-4B9D-82BA-387B90F363F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB7E7876-55E8-4B9D-82BA-387B90F363F6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From c344ab1cc635f09d10c2f8ad945877c1ba8a5359 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 29 Jun 2020 23:58:45 -0400 Subject: [PATCH 50/76] nuget package setting --- src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index 89555d5..fb7449c 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -3,7 +3,7 @@ Exe netcoreapp3.1 - true + false true LICENSE https://github.com/ormico/dbpatchmanager From 0e4805eb65a2ccbad8f4e49ccf684e313b285bb6 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Tue, 30 Jun 2020 00:11:38 -0400 Subject: [PATCH 51/76] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 57984e6..9188e82 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,7 +5,7 @@ pool: variables: system.debug: true buildConfiguration: 'Release' - Version: '2.0.0.$(Build.BuildID)' + Version: '2.1.0.$(Build.BuildID)' steps: - bash: echo v$(Version) - task: DotNetCoreCLI@2 @@ -21,5 +21,5 @@ steps: artifactName: nuget-artifacts - task: PublishBuildArtifacts@1 inputs: - pathtoPublish: 'src/Ormico.DbPatchManager.CLI/bin/release/netcoreapp2.2/publish/' + pathtoPublish: 'src/Ormico.DbPatchManager.CLI/bin/release/netcoreapp3.1/publish/' artifactName: zip-artifacts From 4c0c6d564cfcdde5887190d0820d2bbf8d721a2a Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Thu, 4 Mar 2021 01:52:25 -0500 Subject: [PATCH 52/76] update to .net 5 --- src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj | 2 +- .../Ormico.DbPatchManager.Logic.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index fb7449c..f5c471a 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net5.0 false true LICENSE diff --git a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj index f3e5e8e..ca7008a 100644 --- a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj +++ b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.2 + net5.0 false From 7ebbd6e06ecb6c024b351700b90810be31403264 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Thu, 4 Mar 2021 02:02:15 -0500 Subject: [PATCH 53/76] update nuget and switch to .net 5 --- .../Ormico.DbPatchManager.CLI.csproj | 3 ++- .../PublishProfiles/FolderProfile.pubxml | 4 ++-- .../Ormico.DbPatchManager.Logic.Tests.csproj | 6 ++--- .../DatabaseOptions.cs | 24 +++++++++++++++++++ src/Ormico.DbPatchManager.Logic/IDatabase.cs | 21 ++++++++++++++++ .../InstalledPatchInfo.cs | 14 +++++++++++ .../Ormico.DbPatchManager.Logic.csproj | 8 +++---- 7 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs create mode 100644 src/Ormico.DbPatchManager.Logic/IDatabase.cs create mode 100644 src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index f5c471a..1c9ce5f 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -38,7 +38,8 @@ - + + diff --git a/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml b/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml index 47d90a6..4931fbe 100644 --- a/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release Any CPU - netcoreapp3.1 - bin\Release\netcoreapp3.1\publish\ + net5.0 + bin\Release\net5\publish\ false \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj index ca7008a..8611704 100644 --- a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj +++ b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs b/src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs new file mode 100644 index 0000000..417fb2f --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ormico.DbPatchManager.Logic +{ + public struct DatabaseOptions + { + //public DatabaseOptions() + //{ + // AdditionalOptions = new Dictionary(); + //} + + public string ConnectionString { get; set; } + public string GetInstalledPatchesSql { get; set; } + public string AddInstalledPatchSql { get; set; } + public string InitPatchTableSql { get; set; } + + + public Dictionary AdditionalOptions { get; set; } + } +} diff --git a/src/Ormico.DbPatchManager.Logic/IDatabase.cs b/src/Ormico.DbPatchManager.Logic/IDatabase.cs new file mode 100644 index 0000000..7ffb5aa --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic/IDatabase.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ormico.DbPatchManager.Logic +{ + public interface IDatabase: IDisposable + { + void Connect(DatabaseOptions Options); + + void ExecuteDDL(string commandText); + + List GetInstalledPatches(); + + void LogInstalledPatch(string patchId); + + void ExecuteProgrammabilityScript(string commandText); + } +} diff --git a/src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs b/src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs new file mode 100644 index 0000000..4c4a073 --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ormico.DbPatchManager.Logic +{ + public class InstalledPatchInfo + { + public string PatchId { get; set; } + public DateTime InstalledDate { get; set; } + } +} diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index d20aead..43310ba 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -17,10 +17,10 @@ - - - - + + + + From 275b3b8c9e31fd07bba76f634f01e87688775375 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 00:47:32 -0500 Subject: [PATCH 54/76] linux install script & upgrade to .net5 --- .gitignore | 2 + src/Ormico.DbPatchManager.CLI/dbpatch | 3 +- .../Ormico.DbPatchManager.Logic.Tests.csproj | 10 ++--- .../UnitTest1.cs | 17 +++++--- src/Ormico.DbPatchManager.sln | 12 +++--- src/install-dbpatch.sh | 43 +++++++++++++++++++ 6 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 src/install-dbpatch.sh diff --git a/.gitignore b/.gitignore index f717ca9..55986f9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ src/Ormico.DbPatchManager/bin/* src/Ormico.DbPatchManager/obj/* bin obj + +src/Ormico.DbPatchManager.CLI/deb/ diff --git a/src/Ormico.DbPatchManager.CLI/dbpatch b/src/Ormico.DbPatchManager.CLI/dbpatch index f30ec68..43934eb 100644 --- a/src/Ormico.DbPatchManager.CLI/dbpatch +++ b/src/Ormico.DbPatchManager.CLI/dbpatch @@ -1,4 +1,5 @@ #!/bin/sh -dotnet ./dbpatch.dll "$@" +# if this isn't working try 'dos2unix dbpatch' +dotnet /usr/local/lib/dbpatch/dbpatch.dll "$@" rc=$? exit $rc \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj index 8611704..5336704 100644 --- a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj +++ b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj @@ -1,4 +1,4 @@ - + net5.0 @@ -7,13 +7,9 @@ + + - - - - - - diff --git a/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs b/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs index 0008e77..a0c3692 100644 --- a/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs +++ b/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs @@ -1,13 +1,18 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; namespace Ormico.DbPatchManager.Logic.Tests { - [TestClass] - public class UnitTest1 + public class Tests { - [TestMethod] - public void TestMethod1() + [SetUp] + public void Setup() { } + + [Test] + public void Test1() + { + Assert.Pass(); + } } -} +} \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.sln b/src/Ormico.DbPatchManager.sln index 37c0270..62c3e3c 100644 --- a/src/Ormico.DbPatchManager.sln +++ b/src/Ormico.DbPatchManager.sln @@ -7,9 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.CLI", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Logic", "Ormico.DbPatchManager.Logic\Ormico.DbPatchManager.Logic.csproj", "{009D6D57-82CD-4617-A0B2-83F70D71E44E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Logic.Tests", "Ormico.DbPatchManager.Logic.Tests\Ormico.DbPatchManager.Logic.Tests.csproj", "{59FD6996-4792-4F29-8F31-5873BC61F974}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Common", "Ormico.DbPatchManager.Common\Ormico.DbPatchManager.Common.csproj", "{EB7E7876-55E8-4B9D-82BA-387B90F363F6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ormico.DbPatchManager.Common", "Ormico.DbPatchManager.Common\Ormico.DbPatchManager.Common.csproj", "{EB7E7876-55E8-4B9D-82BA-387B90F363F6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ormico.DbPatchManager.Logic.Tests", "Ormico.DbPatchManager.Logic.Tests\Ormico.DbPatchManager.Logic.Tests.csproj", "{6ECAE8E2-1FC5-4BFF-9C45-24C0215E35B6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -25,14 +25,14 @@ Global {009D6D57-82CD-4617-A0B2-83F70D71E44E}.Debug|Any CPU.Build.0 = Debug|Any CPU {009D6D57-82CD-4617-A0B2-83F70D71E44E}.Release|Any CPU.ActiveCfg = Release|Any CPU {009D6D57-82CD-4617-A0B2-83F70D71E44E}.Release|Any CPU.Build.0 = Release|Any CPU - {59FD6996-4792-4F29-8F31-5873BC61F974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {59FD6996-4792-4F29-8F31-5873BC61F974}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59FD6996-4792-4F29-8F31-5873BC61F974}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59FD6996-4792-4F29-8F31-5873BC61F974}.Release|Any CPU.Build.0 = Release|Any CPU {EB7E7876-55E8-4B9D-82BA-387B90F363F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EB7E7876-55E8-4B9D-82BA-387B90F363F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {EB7E7876-55E8-4B9D-82BA-387B90F363F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {EB7E7876-55E8-4B9D-82BA-387B90F363F6}.Release|Any CPU.Build.0 = Release|Any CPU + {6ECAE8E2-1FC5-4BFF-9C45-24C0215E35B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6ECAE8E2-1FC5-4BFF-9C45-24C0215E35B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ECAE8E2-1FC5-4BFF-9C45-24C0215E35B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6ECAE8E2-1FC5-4BFF-9C45-24C0215E35B6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/install-dbpatch.sh b/src/install-dbpatch.sh new file mode 100644 index 0000000..af15638 --- /dev/null +++ b/src/install-dbpatch.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Copyright (c) Zack Moore MIT License. All rights reserved. +# https://github.com/ormico/dbpatchmanager +################################################################## +#you can test using the docker dotnet container image +#docker run -it --rm mcr.microsoft.com/dotnet/runtime /bin/bash +################################################################## +#dependencies +#apt-get update && apt-get install -y wget unzip +################################################################## +{ + INSTALL_DIR="/usr/local/lib/dbpatch" + BIN="/usr/local/bin/dbpatch" + + if [ -h $BIN ]; then + echo "=> remove old symlink $BIN" + rm $BIN + fi + + if [ -f $BIN ]; then + echo "=> there is a file at $BIN. install cannot proceed." + exit 1 + fi + + if [ -d $INSTALL_DIR ]; then + echo "=> dbpatch is already installed in $INSTALL_DIR, trying to update" + rm -r $INSTALL_DIR + fi + + mkdir $INSTALL_DIR + # make release zip file dbpatch.zip . don't put version in filename. that way installer won't have to be updated for each version + #wget https://github.com/ormico/dbpatchmanager/releases/download/v2.0.127/dbpatch-v1.0.127.zip -O "$INSTALL_DIR/dbpatch.zip" + #wget https://github.com/ormico/dbpatchmanager/releases/download/v2.0.127/dbpatch.zip -O "$INSTALL_DIR/dbpatch.zip" + wget https://github.com/ormico/dbpatchmanager/releases/latest/download/dbpatch.zip -O /usr/local/lib/dbpatch/dbpatch.zip + #Schmod +x $INSTALL_DIR + unzip "$INSTALL_DIR/dbpatch.zip" -d $INSTALL_DIR + chmod +x "$INSTALL_DIR/dbpatch" + rm "$INSTALL_DIR/dbpatch.zip" + + # make sure file is in linux format so you don't need this dependency + #dos2unix /usr/local/lib/dbpatch/dbpatch + ln -s "$INSTALL_DIR/dbpatch" $BIN +} From 9f43e339ef77f30ff6ecff78103b0397c5fb5401 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 01:16:03 -0500 Subject: [PATCH 55/76] remove commented out code --- src/install-dbpatch.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/src/install-dbpatch.sh b/src/install-dbpatch.sh index af15638..8358d1f 100644 --- a/src/install-dbpatch.sh +++ b/src/install-dbpatch.sh @@ -32,7 +32,6 @@ #wget https://github.com/ormico/dbpatchmanager/releases/download/v2.0.127/dbpatch-v1.0.127.zip -O "$INSTALL_DIR/dbpatch.zip" #wget https://github.com/ormico/dbpatchmanager/releases/download/v2.0.127/dbpatch.zip -O "$INSTALL_DIR/dbpatch.zip" wget https://github.com/ormico/dbpatchmanager/releases/latest/download/dbpatch.zip -O /usr/local/lib/dbpatch/dbpatch.zip - #Schmod +x $INSTALL_DIR unzip "$INSTALL_DIR/dbpatch.zip" -d $INSTALL_DIR chmod +x "$INSTALL_DIR/dbpatch" rm "$INSTALL_DIR/dbpatch.zip" From 96c5cb2ac71ddb43eed0b1c6e9e57175eb43dcff Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 03:24:22 -0500 Subject: [PATCH 56/76] remove redundant classes and add sql provider --- .../AddPatchCmdLineOptions.cs | 2 +- .../Ormico.DbPatchManager.CLI.csproj | 7 +++++- .../DatabaseOptions.cs | 24 ------------------- src/Ormico.DbPatchManager.Logic/IDatabase.cs | 21 ---------------- .../InstalledPatchInfo.cs | 14 ----------- .../Ormico.DbPatchManager.Logic.csproj | 3 ++- .../PluginManager.cs | 12 ++++++++-- .../TestDatabase.cs | 10 ++++++-- 8 files changed, 27 insertions(+), 66 deletions(-) delete mode 100644 src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs delete mode 100644 src/Ormico.DbPatchManager.Logic/IDatabase.cs delete mode 100644 src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs diff --git a/src/Ormico.DbPatchManager.CLI/CommandLineOptions/AddPatchCmdLineOptions.cs b/src/Ormico.DbPatchManager.CLI/CommandLineOptions/AddPatchCmdLineOptions.cs index c159931..6eb8cd9 100644 --- a/src/Ormico.DbPatchManager.CLI/CommandLineOptions/AddPatchCmdLineOptions.cs +++ b/src/Ormico.DbPatchManager.CLI/CommandLineOptions/AddPatchCmdLineOptions.cs @@ -9,7 +9,7 @@ public AddPatchCmdLineOptions() { } - [Option("name", HelpText = "patch name")] + [Option('n', "name", HelpText = "patch name")] public string Name { get; set; } } } \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index 1c9ce5f..fa49a72 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -19,6 +19,12 @@ Command Line Interface for Database Change managment designed for multi-dev/multi-branch. + + + + + + @@ -39,7 +45,6 @@ - diff --git a/src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs b/src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs deleted file mode 100644 index 417fb2f..0000000 --- a/src/Ormico.DbPatchManager.Logic/DatabaseOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Ormico.DbPatchManager.Logic -{ - public struct DatabaseOptions - { - //public DatabaseOptions() - //{ - // AdditionalOptions = new Dictionary(); - //} - - public string ConnectionString { get; set; } - public string GetInstalledPatchesSql { get; set; } - public string AddInstalledPatchSql { get; set; } - public string InitPatchTableSql { get; set; } - - - public Dictionary AdditionalOptions { get; set; } - } -} diff --git a/src/Ormico.DbPatchManager.Logic/IDatabase.cs b/src/Ormico.DbPatchManager.Logic/IDatabase.cs deleted file mode 100644 index 7ffb5aa..0000000 --- a/src/Ormico.DbPatchManager.Logic/IDatabase.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Ormico.DbPatchManager.Logic -{ - public interface IDatabase: IDisposable - { - void Connect(DatabaseOptions Options); - - void ExecuteDDL(string commandText); - - List GetInstalledPatches(); - - void LogInstalledPatch(string patchId); - - void ExecuteProgrammabilityScript(string commandText); - } -} diff --git a/src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs b/src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs deleted file mode 100644 index 4c4a073..0000000 --- a/src/Ormico.DbPatchManager.Logic/InstalledPatchInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Ormico.DbPatchManager.Logic -{ - public class InstalledPatchInfo - { - public string PatchId { get; set; } - public DateTime InstalledDate { get; set; } - } -} diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index 43310ba..7927484 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -19,8 +19,9 @@ + - + diff --git a/src/Ormico.DbPatchManager.Logic/PluginManager.cs b/src/Ormico.DbPatchManager.Logic/PluginManager.cs index 73ca8e0..079734e 100644 --- a/src/Ormico.DbPatchManager.Logic/PluginManager.cs +++ b/src/Ormico.DbPatchManager.Logic/PluginManager.cs @@ -14,15 +14,23 @@ public IDatabase LoadDatabasePlugin(string PluginType) { IDatabase rc = null; if(string.Equals(PluginType, "TestDatabase", StringComparison.OrdinalIgnoreCase) || - string.Equals(PluginType, typeof(TestDatabase).ToString(), StringComparison.OrdinalIgnoreCase)) + string.Equals(PluginType, "test", StringComparison.OrdinalIgnoreCase) || + string.Equals(PluginType, typeof(TestDatabase).ToString(), StringComparison.OrdinalIgnoreCase)) { rc = new TestDatabase(); } else if (string.Equals(PluginType, "OdbcDatabase", StringComparison.OrdinalIgnoreCase) || - string.Equals(PluginType, typeof(OdbcDatabase).ToString(), StringComparison.OrdinalIgnoreCase)) + string.Equals(PluginType, "odbc", StringComparison.OrdinalIgnoreCase) || + string.Equals(PluginType, typeof(OdbcDatabase).ToString(), StringComparison.OrdinalIgnoreCase)) { rc = new OdbcDatabase(); } + else if (string.Equals(PluginType, "SqlDatabase", StringComparison.OrdinalIgnoreCase) || + string.Equals(PluginType, "sqlserver", StringComparison.OrdinalIgnoreCase) || + string.Equals(PluginType, typeof(SqlServer.SqlDatabase).ToString(), StringComparison.OrdinalIgnoreCase)) + { + rc = new SqlServer.SqlDatabase(); + } else { string[] parts = PluginType.Split(','); diff --git a/src/Ormico.DbPatchManager.Logic/TestDatabase.cs b/src/Ormico.DbPatchManager.Logic/TestDatabase.cs index 3edd792..6c19b0e 100644 --- a/src/Ormico.DbPatchManager.Logic/TestDatabase.cs +++ b/src/Ormico.DbPatchManager.Logic/TestDatabase.cs @@ -25,8 +25,14 @@ public void Connect(DatabaseOptions Options) { DbConnectionStringBuilder csb = new DbConnectionStringBuilder(); csb.ConnectionString = Options.ConnectionString; - _fileName = csb["File"] as string; - + if (csb.ContainsKey("File")) + { + _fileName = csb["File"] as string; + } + else + { + _fileName = "test.db"; + } Load(); } From 119d37a946414d20f935dfe24f4319b48f76895c Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 22:56:17 -0500 Subject: [PATCH 57/76] update readme; update install with url for latest --- README.md | 39 ++++++++++++++++++ docs/unblock-zip.png | Bin 0 -> 31712 bytes .../Ormico.DbPatchManager.CLI.csproj | 2 +- .../Ormico.DbPatchManager.Logic.csproj | 3 +- src/install-dbpatch.sh | 3 +- 5 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 docs/unblock-zip.png diff --git a/README.md b/README.md index e0d8601..72330a1 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,45 @@ v2.0 has been updated to .NET Core for cross platform support. DB Patch Manager supports database development across multiple source control branches by tracking the dependencies of each patch and installing them in the correct order. When a developer adds a new patch, that patch depends on all the previous patches recorded in that branch. When branches are merged the `patches.json` file is merged to include all patches and the dependency tree from each branch. +## Installing +### Windows +dbpatch doesn't yet have working distribution package or an msi. + +1. Download the zip from the latest Release. +2. Right click on the zip in Explorer and open the Properties dialog. Click the checkbox to Unblock the zip and then click OK. If you do not see an Unblock checkbox near the bottom of the dialog, then click OK and go to the next step. +3. Unzip the zip file into a folder where you wish to install it. For example `C:\Program Files\dbpatch` +4. Add the folder to your PATH. +5. In the folder you unzipped into, edit `dbpatch.cmd`. If you unzipped into a restricted folder such as Program Files you will need to open your text editor in Administrator mode. +6. In `dbpatch.cmd` change the line `dotnet .\dbpatch.dll %*` to `dotnet "(full path)\dbpatch.dll" %*` where (full path) is the full path fo dbpatch.dll. For example, `dotnet "C:\Program Files\dbpatch\dbpatch.dll" %*` + +![image](docs/unblock-zip.png) + +### Linux +dbpatch doesn't yet have working distribution packages. + +###Prerequisites +* .NET 5 +* wget +* unzip + +``` +wget -qO- https://github.com/ormico/dbpatchmanager/releases/latest/download/install-dbpatch.sh | bash +``` + +or + +``` +wget -q https://github.com/ormico/dbpatchmanager/releases/latest/download/install-dbpatch.sh -O install-dbpatch.sh +chmod +x install-dbpatch.sh +./install-dbpatch.sh +rm install-dbpatch.sh +``` + +If you install dbpatch to somewhere other than `/usr/local/lib/dbpatch` you may need to modify `/usr/local/lib/dbpatch/dbpatch` This file is a shell script which wraps the call to the .net commandline which is used to run dbpatch.dll. + +If you wish to install a version other than latest, each Release comes with an install shell script specific for that version starting with v2.1.1 + +## Build Database changes are deployed using the `build` command. DB Patch Manager will check the database to determine which patches have already been installed before installing new patches in the correct order. ## Create new db project diff --git a/docs/unblock-zip.png b/docs/unblock-zip.png new file mode 100644 index 0000000000000000000000000000000000000000..3c1c7e863f79442213a1fc8ba3104e76b53a2167 GIT binary patch literal 31712 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 Ormico.DbPatchManager.CLI https://dbpatch.dev/ - 2.1.0 + 2.1.1 dbpatch-manager-profile.png Command Line Interface for Database Change managment designed for multi-dev/multi-branch. diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index 7927484..bb0c898 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -11,9 +11,10 @@ https://github.com/ormico/dbpatchmanager Ormico DB Patch Manager Logic https://dbpatch.dev/ - 2.1.0 + 2.1.1 dbpatch-manager-profile.png Logic Library for Database Change managment designed for multi-dev/multi-branch. + 2.1.1.0 diff --git a/src/install-dbpatch.sh b/src/install-dbpatch.sh index 8358d1f..6274605 100644 --- a/src/install-dbpatch.sh +++ b/src/install-dbpatch.sh @@ -29,8 +29,7 @@ mkdir $INSTALL_DIR # make release zip file dbpatch.zip . don't put version in filename. that way installer won't have to be updated for each version - #wget https://github.com/ormico/dbpatchmanager/releases/download/v2.0.127/dbpatch-v1.0.127.zip -O "$INSTALL_DIR/dbpatch.zip" - #wget https://github.com/ormico/dbpatchmanager/releases/download/v2.0.127/dbpatch.zip -O "$INSTALL_DIR/dbpatch.zip" + #wget https://github.com/ormico/dbpatchmanager/releases/download/v2.1.1/dbpatch.zip -O "$INSTALL_DIR/dbpatch.zip" wget https://github.com/ormico/dbpatchmanager/releases/latest/download/dbpatch.zip -O /usr/local/lib/dbpatch/dbpatch.zip unzip "$INSTALL_DIR/dbpatch.zip" -d $INSTALL_DIR chmod +x "$INSTALL_DIR/dbpatch" From 46beb9cc5125d34f98b37351c169fc2b8507f65d Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 22:57:15 -0500 Subject: [PATCH 58/76] remove unnecessary steps from readme --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 72330a1..370c2d4 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,6 @@ dbpatch doesn't yet have working distribution package or an msi. 2. Right click on the zip in Explorer and open the Properties dialog. Click the checkbox to Unblock the zip and then click OK. If you do not see an Unblock checkbox near the bottom of the dialog, then click OK and go to the next step. 3. Unzip the zip file into a folder where you wish to install it. For example `C:\Program Files\dbpatch` 4. Add the folder to your PATH. -5. In the folder you unzipped into, edit `dbpatch.cmd`. If you unzipped into a restricted folder such as Program Files you will need to open your text editor in Administrator mode. -6. In `dbpatch.cmd` change the line `dotnet .\dbpatch.dll %*` to `dotnet "(full path)\dbpatch.dll" %*` where (full path) is the full path fo dbpatch.dll. For example, `dotnet "C:\Program Files\dbpatch\dbpatch.dll" %*` ![image](docs/unblock-zip.png) From f74fabcb666f35975274e7b4ccbe66441b8a64ad Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 23:02:16 -0500 Subject: [PATCH 59/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 370c2d4..a87e3bf 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ dbpatch doesn't yet have working distribution package or an msi. ### Linux dbpatch doesn't yet have working distribution packages. -###Prerequisites +### Prerequisites * .NET 5 * wget * unzip From 36aa6de32d779c4e98056e94c7007c0372a032eb Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 23:06:14 -0500 Subject: [PATCH 60/76] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a87e3bf..c61545b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ If you wish to install a version other than latest, each Release comes with an i Database changes are deployed using the `build` command. DB Patch Manager will check the database to determine which patches have already been installed before installing new patches in the correct order. ## Create new db project -```PS C:\MyProject> .\dbpatch init --dbtype "Ormico.DbPatchManager.SqlServer.dll, Ormico.DbPatchManager.SqlServer.SqlDatabase"``` +```MyProject> dbpatch init --dbtype sqlserver``` This will create a new project file named `patches.json` and initilize it to the SQL Server plugin. @@ -59,9 +59,9 @@ Create a new file named `patches.local.json` When you are a adding files to sour Each developer would enter their local connection string. When deploying, you would enter the production server's connection string. ## Add a database patch -```PS C:\MyProject> .\dbpatch addpatch --name TestPatch``` +```MyProject> dbpatch addpatch -n TestPatch``` -Creates a folder for the patch in `C:\MyProject\Patches\` and adds the patch to the patches.json file. The folder is named using a date time string and a random number and the name. For example something like `201708011412-2403-testpatch`. User can place .sql files in the patch folder and they will be run when the patch is applied. If the user includes more than one patch file, they are run in alphabetical order. +Creates a folder for the patch in `...\MyProject\Patches\` and adds the patch to the patches.json file. The folder is named using a date time string and a random number and the name. For example something like `201708011412-2403-testpatch`. User can place .sql files in the patch folder and they will be run when the patch is applied. If the user includes more than one patch file, they are run in alphabetical order. ## Add a database code item Code items are database items that are applied on each build instead of only once like patches. Typically code items are Stored Procedures, Functions, Views, and Triggers. @@ -85,7 +85,7 @@ The default list of code file extensions and the order they load is: * .trigger3.sql - Trigger ## Build Database -```PS C:\MyProject> .\dbpatch build``` +```MyProject> dbpatch build``` Applies all missing patches and runs all code files. From 5c064b77d93bbf9076aadd154738db3ed7cccde9 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 23:06:40 -0500 Subject: [PATCH 61/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c61545b..7cbc80d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DB Patch Manager Database development tool for change control. -v2.0 has been updated to .NET Core for cross platform support. +v2 has been updated to .NET Core for cross platform support. DB Patch Manager supports database development across multiple source control branches by tracking the dependencies of each patch and installing them in the correct order. When a developer adds a new patch, that patch depends on all the previous patches recorded in that branch. When branches are merged the `patches.json` file is merged to include all patches and the dependency tree from each branch. From a07c4083b8a1b7baba7d4ad13cafdc5692493c22 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 23:07:07 -0500 Subject: [PATCH 62/76] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7cbc80d..e6f3d5c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # DB Patch Manager Database development tool for change control. + v2 has been updated to .NET Core for cross platform support. DB Patch Manager supports database development across multiple source control branches by tracking the dependencies of each patch and installing them in the correct order. When a developer adds a new patch, that patch depends on all the previous patches recorded in that branch. When branches are merged the `patches.json` file is merged to include all patches and the dependency tree from each branch. From 81e955e0e50ff18f18a1c5554b0d062676102068 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 23:08:59 -0500 Subject: [PATCH 63/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6f3d5c..ad1c421 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ dbpatch doesn't yet have working distribution package or an msi. ![image](docs/unblock-zip.png) ### Linux -dbpatch doesn't yet have working distribution packages. +dbpatch doesn't yet have working distribution packages but can be installed using the included install shell script, or download and view the shell script if you wish to perform the steps manually. ### Prerequisites * .NET 5 From 11a7e59e5df86f49accf9e78230ee2ccf0873776 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Mon, 8 Mar 2021 23:20:18 -0500 Subject: [PATCH 64/76] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index ad1c421..59d14f3 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,6 @@ If you install dbpatch to somewhere other than `/usr/local/lib/dbpatch` you may If you wish to install a version other than latest, each Release comes with an install shell script specific for that version starting with v2.1.1 -## Build -Database changes are deployed using the `build` command. DB Patch Manager will check the database to determine which patches have already been installed before installing new patches in the correct order. - ## Create new db project ```MyProject> dbpatch init --dbtype sqlserver``` From e2d1c4b4bbd644f1479f25106ff518b73ead5eb8 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Tue, 9 Mar 2021 00:07:20 -0500 Subject: [PATCH 65/76] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 59d14f3..290a72f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +![rect-logo-small](https://user-images.githubusercontent.com/1731364/110421365-60dec400-806b-11eb-9a53-d783ca935042.png) # DB Patch Manager Database development tool for change control. From d9c18cfdb16906f136d210a6b90793edfef8f174 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Tue, 9 Mar 2021 00:08:41 -0500 Subject: [PATCH 66/76] graphic assets --- assets/rect-logo-small.png | Bin 0 -> 11675 bytes assets/rect-logo.png | Bin 0 -> 26047 bytes assets/repository-open-graph-template.pdn | Bin 0 -> 49750 bytes assets/repository-open-graph.png | Bin 0 -> 28694 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/rect-logo-small.png create mode 100644 assets/rect-logo.png create mode 100644 assets/repository-open-graph-template.pdn create mode 100644 assets/repository-open-graph.png diff --git a/assets/rect-logo-small.png b/assets/rect-logo-small.png new file mode 100644 index 0000000000000000000000000000000000000000..ff148c15fd82cde3f169bed478def39e40e88666 GIT binary patch literal 11675 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 literal 0 HcmV?d00001 diff --git a/assets/rect-logo.png b/assets/rect-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b3b44b2c29ad164d337287594a1b4df30f6b980b GIT binary patch literal 26047 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 W=out>H$)YCTD3=7N~MaHum1xN2bqWf literal 0 HcmV?d00001 diff --git a/assets/repository-open-graph-template.pdn b/assets/repository-open-graph-template.pdn new file mode 100644 index 0000000000000000000000000000000000000000..44246dc63a60b46a61eb424292be60624fb2b0f9 GIT binary patch literal 49750 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 literal 0 HcmV?d00001 diff --git a/assets/repository-open-graph.png b/assets/repository-open-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..c944842fb965900d6ba1c6a6601627d915af6f56 GIT binary patch literal 28694 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 literal 0 HcmV?d00001 From a7eac9a8ad5d3e3a2b8c49c576ccbfba52aaa679 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Tue, 9 Mar 2021 00:09:53 -0500 Subject: [PATCH 67/76] remove old file --- .hgignore | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .hgignore diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 9912a0a..0000000 --- a/.hgignore +++ /dev/null @@ -1,8 +0,0 @@ -syntax: glob -src/.vs/* -src/Ormico.DbPatchManager.Cmd/bin/* -src/Ormico.DbPatchManager.Cmd/obj/* -src/Ormico.DbPatchManager/obj/* -src/Ormico.DbPatchManager/bin/* -src/Ormico.DbPatchManager.SqlServer/bin/* -src/Ormico.DbPatchManager.SqlServer/obj/* From c2ce5bfc004dfb944c64e0db1a39c17bf647bdc4 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Tue, 9 Mar 2021 19:13:16 -0500 Subject: [PATCH 68/76] create tutorial doc --- docs/tutorial.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/tutorial.md diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 0000000..70f4733 --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1 @@ +# tutorial.md From 66c652a8099d7476096001acdc6931c5a185ce7b Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Wed, 10 Mar 2021 02:21:17 -0500 Subject: [PATCH 69/76] working on tutorial --- docs/tutorial-testdbtype.md | 8 ++ docs/tutorial.md | 159 +++++++++++++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 docs/tutorial-testdbtype.md diff --git a/docs/tutorial-testdbtype.md b/docs/tutorial-testdbtype.md new file mode 100644 index 0000000..3872179 --- /dev/null +++ b/docs/tutorial-testdbtype.md @@ -0,0 +1,8 @@ +-> I am considering moving the section on the Test Db to a separate document +## Creating a new Project (Test Db Type) +### Setting the database type +### Setting the database Connection String +### Creating the first Patches +### Building the Database +### Merging schema changes from another user +### Creating the first Database Code entries diff --git a/docs/tutorial.md b/docs/tutorial.md index 70f4733..708a3d5 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1 +1,158 @@ -# tutorial.md +# Tutorial +## Intro +dbpatch is a tool for database change management. The purpose of the tool is to manage database change patches and code assets. These patches and code assets can then be added to and modified in multiple source control branches, merged, and applied to a database in the proper order. + +## Installing +Before using dbpatch, it must be installed on a workstation or server. + +dbpatch is created using .NET 5 which is cross platform and can run on Windows, Linux, and MacOS. + +### Installing on Windows +dbpatch doesn't yet have working distribution package or msi. Instead use the following instructions. + +[.NET 5 runtime](https://dotnet.microsoft.com/download/dotnet/5.0) is a requirement that must be installed before dbpatch can be used. + +1. Download the zip from the latest Release. +2. Right click on the zip in Explorer and open the Properties dialog. Click the checkbox to Unblock the zip and then click OK. If you do not see an Unblock checkbox near the bottom of the dialog, then click OK and go to the next step. +3. Unzip the zip file into a folder where you wish to install it. For example `C:\Program Files\dbpatch` +4. Add the folder to your PATH. + +![image](unblock-zip.png) + +### Installing on Linux +-> I tested on an Ubuntu docker container, WSL Ubuntu. +Need to update sudo usage in instructions. + +dbpatch doesn't yet have working distribution packages but can be installed using the included install shell script, or download and view the shell script if you wish to perform the steps manually. + +#### Prerequisites +* [.NET 5 runtime](https://dotnet.microsoft.com/download/dotnet/5.0) +* [wget](https://www.gnu.org/software/wget/) +* unzip + +#### Steps +Using wget the install shell script can be downloaded and piped to bash to perform the install. This is the quickest way to get dbpatch installed, but it requires trusting the install script. +``` +wget -qO- https://github.com/ormico/dbpatchmanager/releases/latest/download/install-dbpatch.sh | sudo bash +``` +Or, you can download the install script and review it's contents before running. +``` +wget -q https://github.com/ormico/dbpatchmanager/releases/latest/download/install-dbpatch.sh -O install-dbpatch.sh +#review install-dbpatch.sh before executing +cat install-dbpatch.sh +chmod +x install-dbpatch.sh +./install-dbpatch.sh +rm install-dbpatch.sh +``` + +If you install dbpatch to somewhere other than `/usr/local/lib/dbpatch` you may need to modify `/usr/local/lib/dbpatch/dbpatch` This file is a shell script which wraps the call to the .net command line which is used to run dbpatch.dll. + +If you wish to install a version other than latest, each Release comes with an install shell script specific for that version starting with v2.1.1 + +### Installing on MacOS +I haven't tested this on MacOS yet, but the Linux install instructions should be similar. + +## Our Example Developers +In this tutorial we are going to simulate a multi-developer team working in git and SQL Server. To simulate the team collaborating together in git we will check the changes attributed to each of our imaginary team members into a different branch. + +Our Imaginary Team: +* Ann - working on user login and privilege system +* Bettie - working on order tracking system +* Casey - initial project setup and PR review and merge + +## Creating a new Project (SQL Server Type) +To get started, create a folder for the the database project. In that folder run `dbpatch init` and specify the `--dbtype` parameter. For this example specify a MS SQL Server database by using `sqlserver` as the dbtype value. +``` +mkdir mydb +cd mydb +git init +dbpatch init --dbtype sqlserver +```` +This will create the initial starting folders and files which include: +* A `Code` folder for stored procedures, triggers, and other executable parts of a database. +* A `Patches` folder to hold each change script. +* A `patches.json` file which holds the configuration as well as the patch dependency graph. + +### Create an empty database +dbpatch requires that a database exists for it to connect to. dbpatch will not create the database for you if it does not exist. + +In our example, we are using `sqlcmd` from the command line to create our example database. Execute the following from the command line. + +```sqlcmd -S . -Q "create database [dbpatch-example]"``` + +While the above uses `sqlcmd` this can be performed with any SQL Server tool such as SQL Server Management Studio, Azure Data Studio, or VS Code. + +### Setting the database Connection String +dbpatch's configution system supports splitting the configuration information into two files. In the previous step we created `patches.json` using the `init` command. In this step, we are going to create the second file `patches.local.json`. Where `patches.json` is checked into source control, `patches.local.json` typically is not. The local file, is where workstation or server specific settings are specified. When dbpatch runs, these two configuration files are merged with the local file overriding any settings made in `patches.json`. While you can override any setting, typically the `ConnectionString` is the most common setting used in the local file. + +Using a text editor, create a file in the database project folder named `patches.local.json`. The contents of the file should look like the following, but in place of this connection string specify the Connection String to your development database. This is usually a database and server running on your local workstation. + +``` +{ + "ConnectionString": "Server=.;Database=dbpatch-example;Trusted_Connection=True;" +} +``` + +### Ignoring patches.local.json and checking in changes in source control + +``` +echo "patches.local.json" > .gitignore +git stage * +git commit -m first +``` + +git commit output: +//todo: update example w/o testdb & correct patch name +``` +[main (root-commit) 7d7f6e3] first + 8 files changed, 77 insertions(+) + create mode 100644 .gitignore + create mode 100644 Patches/202103082324-1408-p2/p.sql + create mode 100644 Patches/202103082324-2009-p1/p.sql + create mode 100644 Patches/202103082324-7288-p3/p.sql + create mode 100644 Patches/202103082324-9737-p4/p.sql + create mode 100644 patches.json + create mode 100644 sqltools_20210308232445_31788.log + create mode 100644 test.db + ``` + +## Creating the first Patches + +```dbpatch addpatch -n first-patch``` + +```202103082324-1408-first-patch``` +``` +git branch sue/db-changes +git checkout sue/db-changes +git stage * +git commit -m "first patch" +``` + + +//todo: decide what kind of db we are building and create the final whole project in an example repo or folder. make a schema diagram and post it in this example + +### Building the Database after Schema additions + +//todo: make sure dbpatch doesn't do anything if patches.json is not in current folder +```dbpatch build``` + +## Merging schema changes from another user +### Building the Database after Schema merge + +## Examining InstalledPatches table +//todo: add order by installed date column and show query results +```sqlcmd -S . -d dbpatch-example -Q "select * from installedpatches"``` + +## Creating the first Database Code entries +### Stored Procedures +### User Defined Functions +### User Defined Function with Dependencies +### Triggers +### Building the Database after Code additions + +## Merging Code entries +### Building the Database after Code merge + +# Deploying changes to production + +# Conclusion From a37d537e7cca0863376d89a43161edcecde4a2d3 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Thu, 11 Mar 2021 00:31:22 -0500 Subject: [PATCH 70/76] editing tutorial --- docs/tutorial.md | 54 +++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 708a3d5..a80ed54 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -5,14 +5,14 @@ dbpatch is a tool for database change management. The purpose of the tool is to ## Installing Before using dbpatch, it must be installed on a workstation or server. -dbpatch is created using .NET 5 which is cross platform and can run on Windows, Linux, and MacOS. +dbpatch is compiled on .NET 5 which is cross platform and runs on Windows, Linux, and MacOS. ### Installing on Windows -dbpatch doesn't yet have working distribution package or msi. Instead use the following instructions. +dbpatch doesn't yet have a working distribution package or msi for Windows. Instead use the following instructions. [.NET 5 runtime](https://dotnet.microsoft.com/download/dotnet/5.0) is a requirement that must be installed before dbpatch can be used. -1. Download the zip from the latest Release. +1. Download the zip from the latest [Release](https://github.com/ormico/dbpatchmanager/releases/latest/download/dbpatch.zip). 2. Right click on the zip in Explorer and open the Properties dialog. Click the checkbox to Unblock the zip and then click OK. If you do not see an Unblock checkbox near the bottom of the dialog, then click OK and go to the next step. 3. Unzip the zip file into a folder where you wish to install it. For example `C:\Program Files\dbpatch` 4. Add the folder to your PATH. @@ -20,8 +20,8 @@ dbpatch doesn't yet have working distribution package or msi. Instead use the fo ![image](unblock-zip.png) ### Installing on Linux --> I tested on an Ubuntu docker container, WSL Ubuntu. -Need to update sudo usage in instructions. +//todo: I tested on an Ubuntu docker container, WSL Ubuntu. +//todo: Need to update sudo usage in instructions. dbpatch doesn't yet have working distribution packages but can be installed using the included install shell script, or download and view the shell script if you wish to perform the steps manually. @@ -31,21 +31,25 @@ dbpatch doesn't yet have working distribution packages but can be installed usin * unzip #### Steps -Using wget the install shell script can be downloaded and piped to bash to perform the install. This is the quickest way to get dbpatch installed, but it requires trusting the install script. +Using `wget`, the install shell script can be downloaded and piped to bash to perform the install. This is the quickest way to get dbpatch installed, but it requires trusting the install script. ``` wget -qO- https://github.com/ormico/dbpatchmanager/releases/latest/download/install-dbpatch.sh | sudo bash ``` Or, you can download the install script and review it's contents before running. ``` +#download install-dbpatch.sh wget -q https://github.com/ormico/dbpatchmanager/releases/latest/download/install-dbpatch.sh -O install-dbpatch.sh -#review install-dbpatch.sh before executing +#review install-dbpatch.sh code before executing cat install-dbpatch.sh +#set install-dbpatch.sh as executable chmod +x install-dbpatch.sh +#run install-dbpatch.sh ./install-dbpatch.sh +#cleanup rm install-dbpatch.sh ``` -If you install dbpatch to somewhere other than `/usr/local/lib/dbpatch` you may need to modify `/usr/local/lib/dbpatch/dbpatch` This file is a shell script which wraps the call to the .net command line which is used to run dbpatch.dll. +If you install dbpatch to a location other than `/usr/local/lib/dbpatch` you may need to modify `/usr/local/lib/dbpatch/dbpatch`. This file is a shell script which wraps the call to `dotnet dbpatch.dll`. If you wish to install a version other than latest, each Release comes with an install shell script specific for that version starting with v2.1.1 @@ -53,15 +57,16 @@ If you wish to install a version other than latest, each Release comes with an i I haven't tested this on MacOS yet, but the Linux install instructions should be similar. ## Our Example Developers -In this tutorial we are going to simulate a multi-developer team working in git and SQL Server. To simulate the team collaborating together in git we will check the changes attributed to each of our imaginary team members into a different branch. +In this tutorial, we are going to simulate a multi-developer team working in git and SQL Server. To simulate the team collaborating together in git, we will check the changes attributed to each of our imaginary team members into a different branch. Our Imaginary Team: * Ann - working on user login and privilege system * Bettie - working on order tracking system * Casey - initial project setup and PR review and merge -## Creating a new Project (SQL Server Type) -To get started, create a folder for the the database project. In that folder run `dbpatch init` and specify the `--dbtype` parameter. For this example specify a MS SQL Server database by using `sqlserver` as the dbtype value. +## Creating a new Project (SQL Server) +To get started, create a folder for the the database project. In that folder run `dbpatch init` and specify the `--dbtype` parameter. For this example, specify a MS SQL Server database by using `sqlserver` as the dbtype value. + ``` mkdir mydb cd mydb @@ -76,20 +81,26 @@ This will create the initial starting folders and files which include: ### Create an empty database dbpatch requires that a database exists for it to connect to. dbpatch will not create the database for you if it does not exist. -In our example, we are using `sqlcmd` from the command line to create our example database. Execute the following from the command line. +In our example, we are using `sqlcmd` from the command line to create our example database but any SQL Server tool that can execute scripts will do. Execute the following from the command line. -```sqlcmd -S . -Q "create database [dbpatch-example]"``` +``` +sqlcmd -S . -Q "create database [dbpatch-example-ann]" +sqlcmd -S . -Q "create database [dbpatch-example-bettie]" +sqlcmd -S . -Q "create database [dbpatch-example-casey]" +``` -While the above uses `sqlcmd` this can be performed with any SQL Server tool such as SQL Server Management Studio, Azure Data Studio, or VS Code. +Because we have three developers on our team, lets give each of them their own database to work in. This simulates each of them having a local database on their own workstation. ### Setting the database Connection String -dbpatch's configution system supports splitting the configuration information into two files. In the previous step we created `patches.json` using the `init` command. In this step, we are going to create the second file `patches.local.json`. Where `patches.json` is checked into source control, `patches.local.json` typically is not. The local file, is where workstation or server specific settings are specified. When dbpatch runs, these two configuration files are merged with the local file overriding any settings made in `patches.json`. While you can override any setting, typically the `ConnectionString` is the most common setting used in the local file. +dbpatch's configution system supports splitting the configuration information into two files. In the previous step, we created `patches.json` using the `init` command. + +In this step, we are going to create the second file `patches.local.json`. Where `patches.json` is checked into source control, `patches.local.json` is designed not to be checked in. The local file is where workstation or server specific settings are specified. When dbpatch runs, these two configuration files are merged with `patches.local.json` overriding any settings made in `patches.json`. While you can override any setting, typically the `ConnectionString` is the most common setting used in the local file. Using a text editor, create a file in the database project folder named `patches.local.json`. The contents of the file should look like the following, but in place of this connection string specify the Connection String to your development database. This is usually a database and server running on your local workstation. ``` { - "ConnectionString": "Server=.;Database=dbpatch-example;Trusted_Connection=True;" + "ConnectionString": "Server=.;Database=dbpatch-example-casey;Trusted_Connection=True;" } ``` @@ -97,8 +108,13 @@ Using a text editor, create a file in the database project folder named `patches ``` echo "patches.local.json" > .gitignore +git branch casey/db-initial-setup +git checkout casey/db-initial-setup git stage * -git commit -m first +git commit -m "setup dbpatch project" + +git checkout main +//todo merge casey's branch into main ``` git commit output: @@ -122,8 +138,8 @@ git commit output: ```202103082324-1408-first-patch``` ``` -git branch sue/db-changes -git checkout sue/db-changes +git branch casey/db-initial-setup +git checkout casey/db-initial-setup git stage * git commit -m "first patch" ``` From 371a2f32079719952eb9605c3b34ed221c36e2f7 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Sun, 14 Mar 2021 20:12:27 -0400 Subject: [PATCH 71/76] working on tutorial; updating nuget --- docs/tutorial.md | 65 ++++++++++++------- .../Ormico.DbPatchManager.CLI.csproj | 2 +- .../Ormico.DbPatchManager.Common.csproj | 3 +- .../Ormico.DbPatchManager.Logic.csproj | 8 +-- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index a80ed54..02218fe 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -96,7 +96,7 @@ dbpatch's configution system supports splitting the configuration information in In this step, we are going to create the second file `patches.local.json`. Where `patches.json` is checked into source control, `patches.local.json` is designed not to be checked in. The local file is where workstation or server specific settings are specified. When dbpatch runs, these two configuration files are merged with `patches.local.json` overriding any settings made in `patches.json`. While you can override any setting, typically the `ConnectionString` is the most common setting used in the local file. -Using a text editor, create a file in the database project folder named `patches.local.json`. The contents of the file should look like the following, but in place of this connection string specify the Connection String to your development database. This is usually a database and server running on your local workstation. +Create a file in the database project folder named `patches.local.json`. The contents of the file should look like the following, but in place of this connection string specify the connection string to your development database. This is usually a database and server running on your local workstation. ``` { @@ -104,33 +104,50 @@ Using a text editor, create a file in the database project folder named `patches } ``` +For our imaginary team, lets create a separate local file for each developer. We can swap this file in and out as we simulate commands for each individual. Create the following three files using the correct connection strings for your workstation. + +`patches.ann-local.json` +``` +{ + "ConnectionString": "Server=.;Database=dbpatch-example-ann;Trusted_Connection=True;" +} +``` + +`patches.bettie-local.json` +``` +{ + "ConnectionString": "Server=.;Database=dbpatch-example-bettie;Trusted_Connection=True;" +} +``` + +`patches.casey-local.json` +``` +{ + "ConnectionString": "Server=.;Database=dbpatch-example-casey;Trusted_Connection=True;" +} +``` + ### Ignoring patches.local.json and checking in changes in source control +The file `patches.local.json` should be added to `.gitignore` to prevent it from being checked in. In addition to containing sensitive login information, `patches.local.json` is used to customize the login for each environment. + +For this example we also want to ignore the other patches local files we created for each imaginary team member. + +Create a `.gitignore` file that contains the following entries. + +`.gitignore` +``` +patches.local.json +patches.ann-local.json +patches.bettie-local.json +patches.casey-local.json +``` + +Check in the files we have created so far (except for the ones that are ignored). ``` -echo "patches.local.json" > .gitignore -git branch casey/db-initial-setup -git checkout casey/db-initial-setup git stage * -git commit -m "setup dbpatch project" - -git checkout main -//todo merge casey's branch into main -``` - -git commit output: -//todo: update example w/o testdb & correct patch name -``` -[main (root-commit) 7d7f6e3] first - 8 files changed, 77 insertions(+) - create mode 100644 .gitignore - create mode 100644 Patches/202103082324-1408-p2/p.sql - create mode 100644 Patches/202103082324-2009-p1/p.sql - create mode 100644 Patches/202103082324-7288-p3/p.sql - create mode 100644 Patches/202103082324-9737-p4/p.sql - create mode 100644 patches.json - create mode 100644 sqltools_20210308232445_31788.log - create mode 100644 test.db - ``` +git commit -m "init dbpatch project" +``` ## Creating the first Patches diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index 75891ba..ebef62b 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -14,7 +14,7 @@ dbpatch Ormico.DbPatchManager.CLI https://dbpatch.dev/ - 2.1.1 + 2.1.2 dbpatch-manager-profile.png Command Line Interface for Database Change managment designed for multi-dev/multi-branch. diff --git a/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj b/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj index 07f80eb..6194af5 100644 --- a/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj +++ b/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -13,6 +13,7 @@ Classes and interfaces used to implement a DB Patch Manager Database Module. Copyright (c) 2020 Zack Moore https://dbpatch.dev/ + 2.1.2 diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index bb0c898..cbca23e 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -11,18 +11,18 @@ https://github.com/ormico/dbpatchmanager Ormico DB Patch Manager Logic https://dbpatch.dev/ - 2.1.1 + 2.1.2 dbpatch-manager-profile.png Logic Library for Database Change managment designed for multi-dev/multi-branch. - 2.1.1.0 + 2.1.2.0 - + - + From 65dfc722c586614d1ee995f492b40370d7b76545 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 21:15:18 +0000 Subject: [PATCH 72/76] Bump Newtonsoft.Json in /src/Ormico.DbPatchManager.Logic Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.3 to 13.0.1. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.3...13.0.1) --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .../Ormico.DbPatchManager.Logic.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index cbca23e..d7c42df 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -19,7 +19,7 @@ - + From 89f63905638926bc5fc4a2098d2abe4e42477dac Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Sun, 26 Nov 2023 22:15:34 -0500 Subject: [PATCH 73/76] upgrade to .net 8 --- .../Ormico.DbPatchManager.CLI.csproj | 26 +++++++---------- .../PublishProfiles/FolderProfile.pubxml | 4 +-- .../Ormico.DbPatchManager.Common.csproj | 17 +++++------ .../PublishProfiles/FolderProfile.pubxml | 13 +++++++++ .../Ormico.DbPatchManager.Logic.Tests.csproj | 14 ++++----- .../Ormico.DbPatchManager.Logic.csproj | 29 +++++++++---------- src/Ormico.DbPatchManager.sln | 4 +-- 7 files changed, 53 insertions(+), 54 deletions(-) create mode 100644 src/Ormico.DbPatchManager.Common/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj index ebef62b..6b4ab7a 100644 --- a/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj +++ b/src/Ormico.DbPatchManager.CLI/Ormico.DbPatchManager.CLI.csproj @@ -1,39 +1,36 @@  - Exe - net5.0 - false + net8.0 + True true LICENSE https://github.com/ormico/dbpatchmanager - Copyright (c) 2020 Zack Moore + Copyright (c) 2023 Zack Moore Zack Moore Ormico Ormico DB Patch Manager CLI dbpatch Ormico.DbPatchManager.CLI https://dbpatch.dev/ - 2.1.2 + 2.2.0 dbpatch-manager-profile.png Command Line Interface for Database Change managment designed for multi-dev/multi-branch. - - True - + + - PreserveNewest @@ -42,20 +39,17 @@ PreserveNewest - - + - - True - + + - - + \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml b/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml index 4931fbe..e238915 100644 --- a/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml +++ b/src/Ormico.DbPatchManager.CLI/Properties/PublishProfiles/FolderProfile.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release Any CPU - net5.0 - bin\Release\net5\publish\ + net8.0 + bin\Release\net8\publish\ false \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj b/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj index 6194af5..8805154 100644 --- a/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj +++ b/src/Ormico.DbPatchManager.Common/Ormico.DbPatchManager.Common.csproj @@ -1,7 +1,6 @@  - - netstandard2.0 + net8.0 https://github.com/ormico/dbpatchmanager LICENSE Zack Moore @@ -11,20 +10,20 @@ dbpatch-manager-profile.png true Classes and interfaces used to implement a DB Patch Manager Database Module. - Copyright (c) 2020 Zack Moore + Copyright (c) 2023 Zack Moore https://dbpatch.dev/ - 2.1.2 + 2.2.0 - True - + + True - + + - - + \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Common/Properties/PublishProfiles/FolderProfile.pubxml b/src/Ormico.DbPatchManager.Common/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..df5633a --- /dev/null +++ b/src/Ormico.DbPatchManager.Common/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,13 @@ + + + + + Release + Any CPU + bin\Release\net8.0\publish\ + FileSystem + <_TargetId>Folder + + \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj index 5336704..0938aad 100644 --- a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj +++ b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj @@ -1,15 +1,11 @@ - - net5.0 - + net8.0 false - - - - + + + - - + \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index d7c42df..9774609 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -1,43 +1,40 @@  - - netstandard2.0 - false + net8.0 + True true - Copyright (c) 2020 Zack Moore + Copyright (c) 2023 Zack Moore LICENSE Ormico Zack Moore https://github.com/ormico/dbpatchmanager Ormico DB Patch Manager Logic https://dbpatch.dev/ - 2.1.2 + 2.2.0 dbpatch-manager-profile.png Logic Library for Database Change managment designed for multi-dev/multi-branch. 2.1.2.0 - - - + - - + + + - True - + + True - + + - - - + \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.sln b/src/Ormico.DbPatchManager.sln index 62c3e3c..b393e08 100644 --- a/src/Ormico.DbPatchManager.sln +++ b/src/Ormico.DbPatchManager.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28803.352 @@ -9,7 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Logic EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Common", "Ormico.DbPatchManager.Common\Ormico.DbPatchManager.Common.csproj", "{EB7E7876-55E8-4B9D-82BA-387B90F363F6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ormico.DbPatchManager.Logic.Tests", "Ormico.DbPatchManager.Logic.Tests\Ormico.DbPatchManager.Logic.Tests.csproj", "{6ECAE8E2-1FC5-4BFF-9C45-24C0215E35B6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ormico.DbPatchManager.Logic.Tests", "Ormico.DbPatchManager.Logic.Tests\Ormico.DbPatchManager.Logic.Tests.csproj", "{6ECAE8E2-1FC5-4BFF-9C45-24C0215E35B6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 492cdf7c207074b2ad7d06aeb592ab6140bb6459 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Sun, 26 Nov 2023 22:54:56 -0500 Subject: [PATCH 74/76] update logic with latest dbpatch.sqlserver which was updated with .net 8 & dbpatch.common --- .../Ormico.DbPatchManager.Logic.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index 9774609..bd6f55f 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -17,7 +17,7 @@ - + From 8bab5179a4485d9ca5c57943a8c0f279565b30ea Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Tue, 5 Dec 2023 01:45:00 -0500 Subject: [PATCH 75/76] add schema checking for v1 (current) json schema, unit testing, custom Exception obj, BuildConfigurationWriter inject IFileSystem for testing --- .../DbPatchManagerException.cs | 19 ++ .../BuildConfigurationWriterTests.cs | 235 ++++++++++++++++++ .../Ormico.DbPatchManager.Logic.Tests.csproj | 8 +- .../README.md | 22 ++ .../SchemaMapperTests.cs | 36 +++ .../UnitTest1.cs | 18 -- .../BuildConfigurationWriter.cs | 42 +++- .../Ormico.DbPatchManager.Logic.csproj | 2 +- .../SchemaMapper.cs | 39 +++ 9 files changed, 395 insertions(+), 26 deletions(-) create mode 100644 src/Ormico.DbPatchManager.Common/DbPatchManagerException.cs create mode 100644 src/Ormico.DbPatchManager.Logic.Tests/BuildConfigurationWriterTests.cs create mode 100644 src/Ormico.DbPatchManager.Logic.Tests/README.md create mode 100644 src/Ormico.DbPatchManager.Logic.Tests/SchemaMapperTests.cs delete mode 100644 src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs create mode 100644 src/Ormico.DbPatchManager.Logic/SchemaMapper.cs diff --git a/src/Ormico.DbPatchManager.Common/DbPatchManagerException.cs b/src/Ormico.DbPatchManager.Common/DbPatchManagerException.cs new file mode 100644 index 0000000..69e11de --- /dev/null +++ b/src/Ormico.DbPatchManager.Common/DbPatchManagerException.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ormico.DbPatchManager.Common +{ + public class DbPatchManagerException : Exception + { + public DbPatchManagerException() : base() + { + } + + public DbPatchManagerException(string message) : base(message) + { + } + + public DbPatchManagerException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Ormico.DbPatchManager.Logic.Tests/BuildConfigurationWriterTests.cs b/src/Ormico.DbPatchManager.Logic.Tests/BuildConfigurationWriterTests.cs new file mode 100644 index 0000000..6494027 --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic.Tests/BuildConfigurationWriterTests.cs @@ -0,0 +1,235 @@ +using NUnit.Framework; +using Ormico.DbPatchManager.Logic; +using System; +using System.Collections.Generic; +using System.IO.Abstractions.TestingHelpers; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ormico.DbPatchManager.Logic.Tests +{ + internal class BuildConfigurationWriterTests + { + [SetUp] + public void Setup() + { + } + + const string patchesJson = @"{ + ""DatabaseType"": ""sqlserver"", + ""ConnectionString"": null, + ""CodeFolder"": ""Code"", + ""CodeFiles"": [ + ""*.view.sql"", + ""*.udf.sql"", + ""*.view2.sql"", + ""*.udf2.sql"", + ""*.view3.sql"", + ""*.udf3.sql"", + ""*.sproc.sql"", + ""*.sproc2.sql"", + ""*.sproc3.sql"", + ""*.trigger.sql"", + ""*.trigger2.sql"", + ""*.trigger3.sql"" + ], + ""PatchFolder"": ""Patches"", + ""patches"": [ + { + ""id"": ""202311262317-6854-first-tables"", + ""dependsOn"": [] + } + ] +}"; + const string patchesJsonWSchema = @"{ + ""$schema"": ""http://dbpatch.com/json-schema/ormico-dbpatch-v1.json"", + ""DatabaseType"": ""sqlserver"", + ""ConnectionString"": null, + ""CodeFolder"": ""Code"", + ""CodeFiles"": [ + ""*.view.sql"", + ""*.udf.sql"", + ""*.view2.sql"", + ""*.udf2.sql"", + ""*.view3.sql"", + ""*.udf3.sql"", + ""*.sproc.sql"", + ""*.sproc2.sql"", + ""*.sproc3.sql"", + ""*.trigger.sql"", + ""*.trigger2.sql"", + ""*.trigger3.sql"" + ], + ""PatchFolder"": ""Patches"", + ""patches"": [ + { + ""id"": ""202311262317-6854-first-tables"", + ""dependsOn"": [] + } + ] +}"; + const string patchesJsonWUnknownSchema = @"{ + ""$schema"": ""http://dbpatch.com/json-schema/ormico-dbpatch-v2.json"", + ""DatabaseType"": ""sqlserver"", + ""PatchFolder"": ""Patches"" +}"; + const string shortPatchesJson = @"{ + ""DatabaseType"": ""sqlserver"", + ""PatchFolder"": ""Patches"" +}"; + const string patchesLocalJson = @"{ + ""DatabaseType"": ""sqlserver"", + ""ConnectionString"": ""Server=devServer;Database=DevDataBase;Trusted_Connection=True;"" +}"; + private const string ExpectedReadApplicationExceptionText = "Configuration file does not exist. Call init first."; + + [Test] + public void DetectSchemaVersion_WhenSchemaPropertyIsSet() + { + var mockFileSystem = new MockFileSystem(new Dictionary + { + { @"\databaseproj\patches.json", new MockFileData("{ }") }, + { @"\databaseproj\patches.local.json", new MockFileData("{ }") } + }); + var bcw = new BuildConfigurationWriter(@"\databaseproj\patches.json", @"\databaseproj\patches.local.json", mockFileSystem); + + var o = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.Parse(patchesJsonWSchema); + + var result = bcw.DetectSchemaVersion(o); + + Assert.That(result, Is.EqualTo(SchemaMapper.PatchesSchemaVersionEnum.DbPatchV1)); + } + + [Test] + public void DetectSchemaVersion_WhenSchemaPropertyIsSetButItIsAnUnknownSchema() + { + var mockFileSystem = new MockFileSystem(new Dictionary + { + { @"\databaseproj\patches.json", new MockFileData("{ }") }, + { @"\databaseproj\patches.local.json", new MockFileData("{ }") } + }); + var bcw = new BuildConfigurationWriter(@"\databaseproj\patches.json", @"\databaseproj\patches.local.json", mockFileSystem); + + var o = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.Parse(patchesJsonWUnknownSchema); + + var result = bcw.DetectSchemaVersion(o); + + Assert.That(result, Is.EqualTo(SchemaMapper.PatchesSchemaVersionEnum.Unknown)); + } + + [Test] + public void DetectSchemaVersion_WhenNoSchemaPropertyIsSetDetectSchemaByOtherProperties() + { + var mockFileSystem = new MockFileSystem(new Dictionary + { + { @"\databaseproj\patches.json", new MockFileData("{ }") }, + { @"\databaseproj\patches.local.json", new MockFileData("{ }") } + }); + var bcw = new BuildConfigurationWriter(@"\databaseproj\patches.json", @"\databaseproj\patches.local.json", mockFileSystem); + + var o = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.Parse(patchesJson); + + var result = bcw.DetectSchemaVersion(o); + + Assert.That(result, Is.EqualTo(SchemaMapper.PatchesSchemaVersionEnum.DbPatchV1)); + } + + [Test] + public void DetectSchemaVersion_WhenNoSchemaPropertyIsSetAndThereArentEnoughOtherPropertiesToDetect() + { + var mockFileSystem = new MockFileSystem(new Dictionary + { + { @"\databaseproj\patches.json", new MockFileData("{ }") }, + { @"\databaseproj\patches.local.json", new MockFileData("{ }") } + }); + var bcw = new BuildConfigurationWriter(@"\databaseproj\patches.json", @"\databaseproj\patches.local.json", mockFileSystem); + + var o = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.Parse(shortPatchesJson); + + var result = bcw.DetectSchemaVersion(o); + + Assert.That(result, Is.EqualTo(SchemaMapper.PatchesSchemaVersionEnum.Unknown)); + + Assert.Pass(); + } + + [Test] + public void Read_WhenFileDoesNotExist() + { + //todo: use https://github.com/TestableIO/System.IO.Abstractions/blob/main/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileWriteAllTextTests.cs + // for examples on how to uses MockFileSystem to test file system operations + + // Arrange + var mockFileSystem = new MockFileSystem(new Dictionary + { + { @"\databaseproj\patches.local.json", new MockFileData("{ }") } + }); + + // Act + //todo: test if null is passed to the constructor as patch and local patch file paths + var bcw = new BuildConfigurationWriter(@"\databaseproj\patches.json", @"\databaseproj\patches.local.json", mockFileSystem); + + // Assert + Assert.Throws(Is.TypeOf().And.Message.EqualTo(ExpectedReadApplicationExceptionText), + () => bcw.Read()); + } + + [Test] + public void Read_WhenFileExists() + { + var mockFileSystem = new MockFileSystem(new Dictionary + { + { @"\databaseproj\patches.json", new MockFileData(patchesJson) }, + { @"\databaseproj\patches.local.json", new MockFileData("{ }") } + }); + var bcw = new BuildConfigurationWriter(@"\databaseproj\patches.json", @"\databaseproj\patches.local.json", mockFileSystem); + + var result = bcw.Read(); + + Assert.That(result.DatabaseType, Is.EqualTo("sqlserver")); + Assert.That(result.CodeFiles[2], Is.EqualTo("*.view2.sql")); + Assert.That(result.CodeFolder, Is.EqualTo("Code")); + Assert.That(result.PatchFolder, Is.EqualTo("Patches")); + Assert.That(result.patches.Count, Is.EqualTo(1)); + } + + [Test] + public void Read_WhenNoLocalFileExists() + { + var mockFileSystem = new MockFileSystem(new Dictionary + { + { @"\databaseproj\patches.json", new MockFileData(patchesJson) } + }); + var bcw = new BuildConfigurationWriter(@"\databaseproj\patches.json", @"\databaseproj\patches.local.json", mockFileSystem); + + var result = bcw.Read(); + + Assert.That(result.DatabaseType, Is.EqualTo("sqlserver")); + Assert.That(result.CodeFiles[2], Is.EqualTo("*.view2.sql")); + Assert.That(result.CodeFolder, Is.EqualTo("Code")); + Assert.That(result.PatchFolder, Is.EqualTo("Patches")); + Assert.That(result.patches.Count, Is.EqualTo(1)); + } + + [Test] + public void Read_WhenLocalFileExists() + { + var mockFileSystem = new MockFileSystem(new Dictionary + { + { @"\databaseproj\patches.json", new MockFileData(patchesJson) }, + { @"\databaseproj\patches.local.json", new MockFileData(patchesLocalJson) } + }); + var bcw = new BuildConfigurationWriter(@"\databaseproj\patches.json", @"\databaseproj\patches.local.json", mockFileSystem); + + var result = bcw.Read(); + + Assert.That(result.DatabaseType, Is.EqualTo("sqlserver")); + Assert.That(result.CodeFiles[2], Is.EqualTo("*.view2.sql")); + Assert.That(result.CodeFolder, Is.EqualTo("Code")); + Assert.That(result.PatchFolder, Is.EqualTo("Patches")); + Assert.That(result.ConnectionString, Is.EqualTo("Server=devServer;Database=DevDataBase;Trusted_Connection=True;")); + Assert.That(result.patches.Count, Is.EqualTo(1)); + } + } +} diff --git a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj index 0938aad..97febac 100644 --- a/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj +++ b/src/Ormico.DbPatchManager.Logic.Tests/Ormico.DbPatchManager.Logic.Tests.csproj @@ -1,11 +1,15 @@ - + net8.0 false - + + + + + \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic.Tests/README.md b/src/Ormico.DbPatchManager.Logic.Tests/README.md new file mode 100644 index 0000000..2f58417 --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic.Tests/README.md @@ -0,0 +1,22 @@ +# Ormico DB PatchManager Logic Unit Tests Readme + +## Overview + +## Test Framework +NUnit + +## Test Naming Conventions + +Test naming is flexible and is not required to strictly follow the below conventions. However, the following conventions are recommended: + +`[UnitOfWork or MethodName]_[Short description of what is being tested]` + +Examples: +* `public void GetPatch_ReturnsPatch()` +* `public void GetPatch_WhenPatchDoesNotExist()` +* `public void MapSchemaVersion_WhenSchemaVersionDoesNotExist()` +* `public void MapSchemaVersion_WhenSchemaVersionExists()` + +One consequence of this naming convention is that if the method name changes, the test name will not match the new method name unless it is manually changed or refactoring tools become capable enough to do it automatically. + +Underscores are not required if PascalCase is used for the description portion of the test name, but are recomended if it is necessary to add an addtional section to the test name. \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic.Tests/SchemaMapperTests.cs b/src/Ormico.DbPatchManager.Logic.Tests/SchemaMapperTests.cs new file mode 100644 index 0000000..04e21e7 --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic.Tests/SchemaMapperTests.cs @@ -0,0 +1,36 @@ +using NUnit.Framework; +using Ormico.DbPatchManager.Logic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ormico.DbPatchManager.Logic.Tests +{ + internal class SchemaMapperTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void MapSchemaVersion_WhenSchemaVersionExists() + { + var schemaMapper = new SchemaMapper(); + var enumResult = schemaMapper.MapSchemaVersion(SchemaMapper.PatchesSchemaVersionId.DbPatchV1); + + Assert.That(enumResult, Is.EqualTo(SchemaMapper.PatchesSchemaVersionEnum.DbPatchV1)); + } + + [Test] + public void MapSchemaVersion_WhenSchemaVersionDoesNotExist() + { + var schemaMapper = new SchemaMapper(); + var enumResult = schemaMapper.MapSchemaVersion("https://example.com/json-schema/does-not-exist.json"); + + Assert.That(enumResult, Is.EqualTo(SchemaMapper.PatchesSchemaVersionEnum.Unknown)); + } + } +} diff --git a/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs b/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs deleted file mode 100644 index a0c3692..0000000 --- a/src/Ormico.DbPatchManager.Logic.Tests/UnitTest1.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NUnit.Framework; - -namespace Ormico.DbPatchManager.Logic.Tests -{ - public class Tests - { - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } - } -} \ No newline at end of file diff --git a/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs b/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs index dba9bb5..f8461a1 100644 --- a/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs +++ b/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs @@ -1,11 +1,10 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Ormico.DbPatchManager.Common; using System; using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Ormico.DbPatchManager.Logic { @@ -14,16 +13,17 @@ namespace Ormico.DbPatchManager.Logic /// public class BuildConfigurationWriter { - public BuildConfigurationWriter(string filePath, string localFilePath) + public BuildConfigurationWriter(string filePath, string localFilePath, IFileSystem fileSystem = null) { _filePath = filePath; _localFilePath = localFilePath; + _io = fileSystem ?? new FileSystem(); } /// /// Use System.IO.Abstraction to make testing easier. /// - FileSystem _io = new FileSystem(); + IFileSystem _io; /// /// Path and name of file to read and write. @@ -48,9 +48,12 @@ public DatabaseBuildConfiguration Read() DatabaseBuildConfiguration rc = null; if(_io.File.Exists(_filePath)) { - //rc = JsonConvert.DeserializeObject(_io.File.ReadAllText(_filePath), _jsonSettings); var o = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.Parse(_io.File.ReadAllText(_filePath)); + var schemaVer = DetectSchemaVersion(o); + if (schemaVer == SchemaMapper.PatchesSchemaVersionEnum.Unknown) + throw new DbPatchManagerException("Cannot detect patches.json schema version"); + if (_io.File.Exists(_localFilePath)) { var localO = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.Parse(_io.File.ReadAllText(_localFilePath)); @@ -110,6 +113,35 @@ from a in patches return rc; } + public SchemaMapper.PatchesSchemaVersionEnum DetectSchemaVersion(Newtonsoft.Json.Linq.JObject o) + { + //todo: write unit tests for this method and SchemaMapper class + var mapper = new SchemaMapper(); + var rc = SchemaMapper.PatchesSchemaVersionEnum.Unknown; + var schemaVersionProperty = o.Property("schema"); + + if (schemaVersionProperty != null) + { + if (!string.IsNullOrWhiteSpace((string)schemaVersionProperty.Value)) + { + string schemaStr = (string)schemaVersionProperty.Value; + rc = mapper.MapSchemaVersion(schemaStr); + } + } + else + { + // if no schemaVersion property then try to detect if v1 by checking other properties + var patchesProperty = o.Property("patches"); + + if (patchesProperty != null) + { + rc = SchemaMapper.PatchesSchemaVersionEnum.DbPatchV1; + } + } + + return rc; + } + public void Write(DatabaseBuildConfiguration buildConfiguration) { JObject data; diff --git a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj index bd6f55f..2282ce3 100644 --- a/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj +++ b/src/Ormico.DbPatchManager.Logic/Ormico.DbPatchManager.Logic.csproj @@ -18,9 +18,9 @@ - + diff --git a/src/Ormico.DbPatchManager.Logic/SchemaMapper.cs b/src/Ormico.DbPatchManager.Logic/SchemaMapper.cs new file mode 100644 index 0000000..2730e96 --- /dev/null +++ b/src/Ormico.DbPatchManager.Logic/SchemaMapper.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ormico.DbPatchManager.Logic +{ + public class SchemaMapper + { + public SchemaMapper() + { + } + + public PatchesSchemaVersionEnum MapSchemaVersion(string schemaName) + { + PatchesSchemaVersionEnum rc = PatchesSchemaVersionEnum.Unknown; + if (schemaNameToEnum.ContainsKey(schemaName)) + rc = schemaNameToEnum[schemaName]; + return rc; + } + + Dictionary schemaNameToEnum = new Dictionary() + { + { PatchesSchemaVersionId.DbPatchV1, PatchesSchemaVersionEnum.DbPatchV1 } + }; + + public enum PatchesSchemaVersionEnum + { + Unknown = 0, + DbPatchV1 = 1 + } + + public class PatchesSchemaVersionId + { + public const string DbPatchV1 = "http://dbpatch.com/json-schema/ormico-dbpatch-v1.json"; + } + } +} From 6345937d220fa89674c61e9e7856d7bddb541296 Mon Sep 17 00:00:00 2001 From: Zack Moore <1731364+ormico@users.noreply.github.com> Date: Wed, 6 Dec 2023 01:51:14 -0500 Subject: [PATCH 76/76] update comments --- .../BuildConfigurationWriter.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs b/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs index f8461a1..cedb2d6 100644 --- a/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs +++ b/src/Ormico.DbPatchManager.Logic/BuildConfigurationWriter.cs @@ -9,10 +9,16 @@ namespace Ormico.DbPatchManager.Logic { /// - /// Readn and Write DatabaseBuildConfiguration to storage. + /// Read and Write DatabaseBuildConfiguration to storage. /// public class BuildConfigurationWriter { + /// + /// + /// + /// Path and filename for main settings file. + /// Path and filename for local override settings file. + /// File system object for unit testing. public BuildConfigurationWriter(string filePath, string localFilePath, IFileSystem fileSystem = null) { _filePath = filePath; @@ -142,11 +148,18 @@ public SchemaMapper.PatchesSchemaVersionEnum DetectSchemaVersion(Newtonsoft.Json return rc; } + /// + /// Write DatabaseBuildConfiguration data to file path passed to constructor. + /// This method is used for all edits which currently include init and add patch. + /// The algorithm currently used writes the entire file each time. The algorithm decides whether to + /// write each property by checking first to see if the property exists in the local file. If it does + /// not exist in the local file then the property is written to the main file. + /// + /// public void Write(DatabaseBuildConfiguration buildConfiguration) { JObject data; - //todo: if local file exists, don't write values to patches.json if value exists in patches.local.json if (_io.File.Exists(_localFilePath)) { var localO = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.Parse(_io.File.ReadAllText(_localFilePath)); @@ -196,7 +209,6 @@ public void Write(DatabaseBuildConfiguration buildConfiguration) } else { - //string data = JsonConvert.SerializeObject(buildConfiguration, Formatting.Indented, _jsonSettings); data = JObject.FromObject(new { DatabaseType = buildConfiguration.DatabaseType, @@ -215,6 +227,9 @@ public void Write(DatabaseBuildConfiguration buildConfiguration) }); } + // write all text creates the file if it doesn't exist + // but it also truncates the file before writing if it does exist + // so there will be no data left over from the original contents. _io.File.WriteAllText(_filePath, data.ToString()); } }