├── .azure-pipelines ├── pull-request.yml ├── release.yml └── templates │ ├── osx │ ├── build-and-unit-test.yml │ ├── functional-test.yml │ ├── pack.signed │ │ ├── step1-layout.yml │ │ ├── step2-signlayout.yml │ │ ├── step3-pack.yml │ │ ├── step4-signpack.yml │ │ └── step5-dist.yml │ └── pack.unsigned.yml │ └── win │ ├── build-and-unit-test.yml │ ├── functional-test.yml │ ├── install-git-package.bat │ ├── install-git-package.sh │ ├── pack.signed.yml │ └── pack.unsigned.yml ├── .editorconfig ├── .gitattributes ├── .github ├── run_esrp_signing.py └── workflows │ ├── build-signed-deb.yml │ ├── continuous-integration.yml │ ├── homebrew-release.yml │ └── stale.yml ├── .gitignore ├── .vsconfig ├── AuthoringTests.md ├── CONTRIBUTING.md ├── Dependencies.props ├── Directory.Build.props ├── Directory.Build.targets ├── Images ├── scalar-card.png ├── scalar-card.svg ├── scalar-large.png ├── scalar.ico └── scalar.svg ├── License.md ├── Protocol.md ├── Readme.md ├── SECURITY.md ├── Scalar.FunctionalTests ├── AssemblyAttributes.cs ├── Categories.cs ├── FileSystemRunners │ ├── BashRunner.cs │ ├── CmdRunner.cs │ ├── FileSystemRunner.cs │ ├── PowerShellRunner.cs │ ├── ShellRunner.cs │ └── SystemIORunner.cs ├── GlobalSetup.cs ├── NUnitRunner.cs ├── Program.cs ├── Scalar.FunctionalTests.csproj ├── ScalarTestConfig.cs ├── Settings.cs ├── Should │ └── FileSystemShouldExtensions.cs ├── Tests │ ├── EnlistmentPerFixture │ │ ├── CloneTests.cs │ │ ├── DiagnoseTests.cs │ │ ├── FetchStepTests.cs │ │ ├── FetchStepWithoutSharedCacheTests.cs │ │ ├── GitCorruptObjectTests.cs │ │ ├── GitFetchTests.cs │ │ ├── GitMoveRenameTests.cs │ │ ├── LooseObjectStepTests.cs │ │ ├── SparseSetTests.cs │ │ ├── StatusVerbTests.cs │ │ └── TestsWithEnlistmentPerFixture.cs │ ├── EnlistmentPerTestCase │ │ └── TestsWithEnlistmentPerTestCase.cs │ ├── GitRepoPerFixture │ │ └── TestsWithGitRepoPerFixture.cs │ ├── MultiEnlistmentTests │ │ ├── ConfigVerbTests.cs │ │ ├── RepoRegistryTests.cs │ │ ├── ScalarCloneFromGithub.cs │ │ ├── SharedCacheTests.cs │ │ └── TestsWithMultiEnlistment.cs │ ├── PrintTestCaseStats.cs │ └── TestResultsHelper.cs └── Tools │ ├── ControlGitRepo.cs │ ├── FileSystemHelpers.cs │ ├── GitHelpers.cs │ ├── GitProcess.cs │ ├── ProcessHelper.cs │ ├── ProcessResult.cs │ ├── RepositoryHelpers.cs │ ├── ScalarFunctionalTestEnlistment.cs │ ├── ScalarHelpers.cs │ ├── ScalarProcess.cs │ └── TestConstants.cs ├── Scalar.MSBuild ├── CompileTemplatedFile.cs ├── GenerateScalarConstants.cs ├── GenerateWindowsAppManifest.cs ├── Scalar.MSBuild.csproj ├── Scalar.targets └── Scalar.tasks ├── Scalar.Signing ├── PostSignFiles.Mac.cmd ├── PreSignFiles.Mac.cmd ├── Scalar.SignFiles.Mac.csproj ├── Scalar.SignFiles.Windows.csproj ├── Scalar.SignInstaller.Windows.csproj ├── Scalar.SignPackage.Mac.csproj └── notarize-pkg.sh ├── Scalar.TestInfrastructure ├── DataSources.cs ├── Scalar.TestInfrastructure.csproj └── Should │ ├── EnumerableShouldExtensions.cs │ ├── StringExtensions.cs │ ├── StringShouldExtensions.cs │ └── ValueShouldExtensions.cs ├── Scalar.ruleset ├── Scalar.sln ├── Scripts ├── BuildScalarForWindows.bat ├── CI │ ├── CreateFTDrop.bat │ ├── CreateInstallerDrop.bat │ └── Set-Version.ps1 ├── CapturePerfView.bat ├── InitializeEnvironment.bat ├── Linux │ ├── BuildScalarForLinux.sh │ ├── CleanupFunctionalTests.sh │ ├── InitializeEnvironment.sh │ ├── InstallFromSource.sh │ ├── NukeBuildOutputs.sh │ └── RunFunctionalTests.sh ├── Mac │ ├── BuildScalarForMac.sh │ ├── CI │ │ ├── CreateFTDrop.sh │ │ └── CreateInstallerDrop.sh │ ├── CleanupFunctionalTests.sh │ ├── InitializeEnvironment.sh │ ├── NukeBuildOutputs.sh │ ├── RunFunctionalTests.sh │ └── Tracing │ │ ├── Docs │ │ └── cddl1.txt │ │ ├── Tracing.md │ │ └── dtruss ├── NukeBuildOutputs.bat ├── ReinstallScalar.bat ├── RestorePackages.bat ├── RunFunctionalTests.bat ├── StopAllServices.bat ├── StopService.bat └── UninstallScalar.bat ├── Signing.targets ├── docs ├── faq.md ├── getting-started.md ├── index.md ├── philosophy.md └── troubleshooting.md ├── global.json └── nuget.config /.azure-pipelines/pull-request.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - main 3 | - releases/* 4 | 5 | pr: 6 | - main 7 | - milestones/* 8 | - releases/* 9 | - servicing/* 10 | 11 | variables: 12 | - group: apple-development 13 | - name: branchCounter 14 | value: $[counter(variables['Build.SourceBranch'], 0)] 15 | - name: configuration 16 | value: Release 17 | 18 | jobs: 19 | - job: win_build 20 | displayName: Windows Build and Unit Test 21 | pool: 22 | vmImage: windows-2019 23 | steps: 24 | - powershell: Scripts/CI/Set-Version.ps1 -SourceBranchCounter $(branchCounter) 25 | displayName: "Compute product version" 26 | - template: templates/win/build-and-unit-test.yml 27 | - template: templates/win/pack.unsigned.yml 28 | 29 | - job: osx_build 30 | displayName: macOS Build and Unit Test 31 | pool: 32 | name: 'Hosted macOS' 33 | steps: 34 | - powershell: Scripts/CI/Set-Version.ps1 -SourceBranchCounter $(branchCounter) 35 | displayName: "Compute product version" 36 | - template: templates/osx/build-and-unit-test.yml 37 | - template: templates/osx/pack.unsigned.yml 38 | 39 | - job: win_functionaltest 40 | displayName: Windows Functional Test 41 | timeoutInMinutes: 45 42 | variables: 43 | configuration: Release 44 | pool: 45 | vmImage: windows-2019 46 | dependsOn: win_build 47 | condition: succeeded() 48 | steps: 49 | - checkout: none 50 | - template: templates/win/functional-test.yml 51 | parameters: 52 | useWatchman: false 53 | 54 | - job: win_functionaltest_watchman 55 | displayName: Windows Functional Test (with Watchman) 56 | timeoutInMinutes: 45 57 | variables: 58 | configuration: Release 59 | pool: 60 | vmImage: windows-2019 61 | dependsOn: win_build 62 | condition: succeeded() 63 | steps: 64 | - checkout: none 65 | - template: templates/win/functional-test.yml 66 | parameters: 67 | useWatchman: true 68 | 69 | - job: osx_functionaltest 70 | displayName: macOS Functional Test 71 | timeoutInMinutes: 45 72 | pool: 73 | name: 'Hosted macOS' 74 | dependsOn: osx_build 75 | condition: succeeded() 76 | steps: 77 | - checkout: none 78 | - template: templates/osx/functional-test.yml 79 | parameters: 80 | useWatchman: false 81 | 82 | - job: osx_functionaltest_watchman 83 | displayName: macOS Functional Test (with Watchman) 84 | variables: 85 | configuration: Release 86 | timeoutInMinutes: 30 87 | pool: 88 | name: 'Hosted macOS' 89 | dependsOn: osx_build 90 | condition: succeeded() 91 | steps: 92 | - checkout: none 93 | - template: templates/osx/functional-test.yml 94 | parameters: 95 | useWatchman: true 96 | -------------------------------------------------------------------------------- /.azure-pipelines/release.yml: -------------------------------------------------------------------------------- 1 | trigger: none 2 | 3 | pr: none 4 | 5 | variables: 6 | - group: apple-development 7 | - name: branchCounter 8 | value: $[counter(variables['Build.SourceBranch'], 0)] 9 | - name: configuration 10 | value: Release 11 | - name: signPool 12 | value: VSEng-MicroBuildVS2019 13 | - name: winImage 14 | value: windows-2019 15 | - name: osxImage 16 | value: macOS-10.14 17 | - name: 1ESFeedCredentials 18 | value: '1ESSharedAssets GVFS Feed [Publish]' 19 | 20 | jobs: 21 | - job: win_build 22 | displayName: Windows Build and Unit Test 23 | pool: 24 | name: $(signPool) 25 | steps: 26 | - powershell: Scripts/CI/Set-Version.ps1 -SourceBranchCounter $(branchCounter) 27 | displayName: "Compute product version" 28 | - template: templates/win/build-and-unit-test.yml 29 | - template: templates/win/pack.signed.yml 30 | 31 | - job: osx_build_step1 32 | displayName: macOS Build and Unit Test 33 | pool: 34 | vmImage: $(osxImage) 35 | steps: 36 | - powershell: Scripts/CI/Set-Version.ps1 -SourceBranchCounter $(branchCounter) 37 | displayName: "Compute product version" 38 | - template: templates/osx/build-and-unit-test.yml 39 | - template: templates/osx/pack.signed/step1-layout.yml 40 | 41 | - job: osx_build_step2 42 | displayName: macOS Sign Binaries 43 | pool: 44 | name: $(signPool) 45 | dependsOn: osx_build_step1 46 | steps: 47 | - template: templates/osx/pack.signed/step2-signlayout.yml 48 | 49 | - job: osx_build_step3 50 | displayName: macOS Create Installer 51 | pool: 52 | vmImage: $(osxImage) 53 | dependsOn: osx_build_step2 54 | steps: 55 | - powershell: Scripts/CI/Set-Version.ps1 -SourceBranchCounter $(branchCounter) 56 | displayName: "Compute product version" 57 | - template: templates/osx/pack.signed/step3-pack.yml 58 | 59 | - job: osx_build_step4 60 | displayName: macOS Sign Installer 61 | pool: 62 | name: $(signPool) 63 | dependsOn: osx_build_step3 64 | steps: 65 | - template: templates/osx/pack.signed/step4-signpack.yml 66 | 67 | - job: osx_build_step5 68 | displayName: macOS Create Distribution 69 | pool: 70 | vmImage: $(osxImage) 71 | dependsOn: osx_build_step4 72 | steps: 73 | - template: templates/osx/pack.signed/step5-dist.yml 74 | 75 | - job: win_functionaltest 76 | displayName: Windows Functional Test 77 | timeoutInMinutes: 45 78 | variables: 79 | configuration: Release 80 | pool: 81 | vmImage: $(winImage) 82 | dependsOn: win_build 83 | condition: succeeded() 84 | steps: 85 | - checkout: none 86 | - template: templates/win/functional-test.yml 87 | parameters: 88 | useWatchman: false 89 | 90 | - job: win_functionaltest_watchman 91 | displayName: Windows Functional Test (with Watchman) 92 | timeoutInMinutes: 45 93 | variables: 94 | configuration: Release 95 | pool: 96 | vmImage: $(winImage) 97 | dependsOn: win_build 98 | condition: succeeded() 99 | steps: 100 | - checkout: none 101 | - template: templates/win/functional-test.yml 102 | parameters: 103 | useWatchman: true 104 | 105 | - job: osx_functionaltest 106 | displayName: macOS Functional Test 107 | timeoutInMinutes: 30 108 | pool: 109 | vmImage: $(osxImage) 110 | dependsOn: osx_build_step5 111 | condition: succeeded() 112 | steps: 113 | - checkout: none 114 | - template: templates/osx/functional-test.yml 115 | parameters: 116 | useWatchman: false 117 | 118 | - job: osx_functionaltest_watchman 119 | displayName: macOS Functional Test (with Watchman) 120 | variables: 121 | configuration: Release 122 | timeoutInMinutes: 30 123 | pool: 124 | vmImage: $(osxImage) 125 | dependsOn: osx_build_step5 126 | condition: succeeded() 127 | steps: 128 | - checkout: none 129 | - template: templates/osx/functional-test.yml 130 | parameters: 131 | useWatchman: true 132 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/osx/build-and-unit-test.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: UseDotNet@2 3 | displayName: Use .NET Core SDK 3.1.101 4 | inputs: 5 | packageType: sdk 6 | version: 3.1.101 7 | 8 | - script: $(Build.Repository.LocalPath)/Scripts/Mac/NukeBuildOutputs.sh 9 | displayName: Delete previous build outputs 10 | continueOnError: true 11 | 12 | - script: | 13 | GIT_VERSION=$(grep '' Directory.Build.props | grep -Eo '[0-9.]+(-\w+)*') 14 | mkdir ../out 15 | cd ../out 16 | dotnet new classlib -n Scalar.GitInstaller 17 | cd Scalar.GitInstaller 18 | dotnet add Scalar.GitInstaller.csproj package "GitForMac.GVFS.Installer" --package-directory . --version "$GIT_VERSION" --source "https://pkgs.dev.azure.com/gvfs/ci/_packaging/Dependencies/nuget/v3/index.json" 19 | displayName: Setup Git installer 20 | 21 | - script: Scripts/Mac/BuildScalarForMac.sh $(configuration) $(majorAndMinorVersion).$(revision) 22 | displayName: Build Scalar ($(configuration)) 23 | 24 | - task: PublishTestResults@2 25 | displayName: Publish test results 26 | inputs: 27 | testRunner: VSTest 28 | testResultsFiles: '**/*.trx' 29 | searchFolder: $(Common.TestResultsDirectory) 30 | testRunTitle: Mac $(configuration) Unit Tests 31 | publishRunAttachments: true 32 | condition: succeededOrFailed() 33 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/osx/functional-test.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | useWatchman: true 3 | 4 | steps: 5 | - bash: mkdir -p $(Build.ArtifactStagingDirectory)/logs 6 | displayName: Create logs directory 7 | 8 | - task: UseDotNet@2 9 | displayName: Use .NET Core SDK 3.1.101 10 | inputs: 11 | packageType: sdk 12 | version: 3.1.101 13 | 14 | - task: DownloadPipelineArtifact@2 15 | displayName: Download functional tests drop 16 | inputs: 17 | artifact: FunctionalTests_macOS_$(configuration) 18 | path: $(Build.BinariesDirectory)/ft 19 | 20 | - task: DownloadPipelineArtifact@2 21 | displayName: Download installers drop 22 | inputs: 23 | artifact: Installers_macOS_$(configuration) 24 | path: $(Build.BinariesDirectory)/install 25 | 26 | - bash: | 27 | chmod +x $(Build.BinariesDirectory)/ft/src/Scripts/Mac/*.sh 28 | chmod +x $(Build.BinariesDirectory)/install/*.sh 29 | displayName: Ensure all scripts are executable 30 | 31 | - ${{ if eq(parameters.useWatchman, 'true') }}: 32 | - bash: $(Build.BinariesDirectory)/install/InstallScalar.sh 33 | displayName: Install product (with watchman) 34 | 35 | - ${{ if ne(parameters.useWatchman, 'true') }}: 36 | - bash: $(Build.BinariesDirectory)/install/InstallScalar.sh --no-watchman 37 | displayName: Install product (without watchman) 38 | 39 | - ${{ if eq(parameters.useWatchman, 'true') }}: 40 | - bash: watchman log-level debug 41 | displayName: Set watchman log level to debug 42 | 43 | - bash: $(Build.BinariesDirectory)/ft/src/Scripts/Mac/RunFunctionalTests.sh $(configuration) --full-suite --test-scalar-on-path --trace2-output=$(Build.ArtifactStagingDirectory)/logs/trace2-event.log 44 | displayName: Run functional tests 45 | 46 | - ${{ if eq(parameters.useWatchman, 'true') }}: 47 | - task: PublishTestResults@2 48 | displayName: Publish functional tests results 49 | inputs: 50 | testRunner: NUnit 51 | testResultsFiles: "**\\TestResult*.xml" 52 | searchFolder: $(System.DefaultWorkingDirectory) 53 | testRunTitle: macOS $(configuration) Functional Tests (with watchman) 54 | publishRunAttachments: true 55 | condition: succeededOrFailed() 56 | 57 | - ${{ if ne(parameters.useWatchman, 'true') }}: 58 | - task: PublishTestResults@2 59 | displayName: Publish functional tests results 60 | inputs: 61 | testRunner: NUnit 62 | testResultsFiles: "**\\TestResult*.xml" 63 | searchFolder: $(System.DefaultWorkingDirectory) 64 | testRunTitle: macOS $(configuration) Functional Tests (without watchman) 65 | publishRunAttachments: true 66 | condition: succeededOrFailed() 67 | 68 | - ${{ if eq(parameters.useWatchman, 'true') }}: 69 | - bash: cp -pf '/usr/local/var/run/watchman/runner-state/log' '$(Build.ArtifactStagingDirectory)/logs/watchman-log' 70 | displayName: Copy watchman logs 71 | condition: failed() 72 | 73 | - ${{ if eq(parameters.useWatchman, 'true') }}: 74 | - task: PublishPipelineArtifact@1 75 | displayName: Publish test and installation logs 76 | inputs: 77 | targetPath: $(Build.ArtifactStagingDirectory)/logs 78 | artifactName: Logs_macOS_Watchman 79 | condition: failed() 80 | 81 | - ${{ if ne(parameters.useWatchman, 'true') }}: 82 | - task: PublishPipelineArtifact@1 83 | displayName: Publish test and installation logs 84 | inputs: 85 | targetPath: $(Build.ArtifactStagingDirectory)/logs 86 | artifactName: Logs_macOS 87 | condition: failed() 88 | 89 | - bash: $(Build.BinariesDirectory)/ft/src/Scripts/Mac/CleanupFunctionalTests.sh 90 | displayName: Cleanup 91 | condition: always() 92 | 93 | - bash: sudo rm -rf $(Build.BinariesDirectory)/ft 94 | displayName: Cleanup phase 2 95 | condition: always() 96 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/osx/pack.signed/step1-layout.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: Scripts/Mac/CI/CreateFTDrop.sh $(configuration) $(Build.ArtifactStagingDirectory)/Tests 3 | displayName: Create functional tests drop 4 | 5 | - task: PublishPipelineArtifact@1 6 | displayName: Publish functional tests drop 7 | inputs: 8 | targetPath: $(Build.ArtifactStagingDirectory)/Tests 9 | artifactName: FunctionalTests_macOS_$(configuration) 10 | condition: succeeded() 11 | 12 | - script: Scalar.Installer.Mac/layout.sh $(configuration) 'netcoreapp3.1' 'osx-x64' '$(Build.SourcesDirectory)' '$(Build.SourcesDirectory)/../out' '$(Build.ArtifactStagingDirectory)/layout' 13 | displayName: Layout installer payload 14 | 15 | - task: PublishPipelineArtifact@1 16 | displayName: Publish unsigned payload 17 | inputs: 18 | targetPath: $(Build.ArtifactStagingDirectory)/layout 19 | artifactName: _osx_payload_unsigned 20 | condition: succeeded() 21 | 22 | - task: PublishPipelineArtifact@1 23 | displayName: Publish Git for Mac package content 24 | inputs: 25 | targetPath: ../out/Scalar.Installer.Mac/dist/$(configuration)/Git 26 | artifactName: _osx_gitformac_package 27 | condition: succeeded() 28 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/osx/pack.signed/step2-signlayout.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: ms-vseng.MicroBuildTasks.30666190-6959-11e5-9f96-f56098202fef.MicroBuildSigningPlugin@2 3 | displayName: Install signing plugin 4 | inputs: 5 | signType: $(SignType) 6 | 7 | - task: UseDotNet@2 8 | displayName: Use .NET Core SDK 3.1.101 9 | inputs: 10 | packageType: sdk 11 | version: 3.1.101 12 | 13 | - task: DownloadPipelineArtifact@2 14 | displayName: Download unsigned payload 15 | inputs: 16 | artifact: _osx_payload_unsigned 17 | path: $(Build.ArtifactStagingDirectory)\layout 18 | 19 | - script: Scalar.Signing\PreSignFiles.Mac.cmd "$(Build.ArtifactStagingDirectory)\layout" "$(Build.ArtifactStagingDirectory)\signroot" 20 | displayName: Collect files to sign 21 | 22 | # Must use the NuGet & MSBuild toolchain here rather than `dotnet` 23 | # because the signing tasks target the netfx MSBuild runtime only. 24 | - task: NuGetCommand@2 25 | displayName: Restore MicroBuild packages 26 | inputs: 27 | command: restore 28 | restoreSolution: 'Scalar.Signing\Scalar.SignFiles.Mac.csproj' 29 | 30 | - task: MSBuild@1 31 | displayName: Sign payload 32 | inputs: 33 | solution: 'Scalar.Signing\Scalar.SignFiles.Mac.csproj' 34 | msbuildArguments: '/p:RootDir="$(Build.ArtifactStagingDirectory)\signroot"' 35 | 36 | - script: Scalar.Signing\PostSignFiles.Mac.cmd "$(Build.ArtifactStagingDirectory)\signroot" "$(Build.ArtifactStagingDirectory)\layout" 37 | displayName: Update layout with signed files 38 | 39 | - task: PublishPipelineArtifact@1 40 | displayName: Publish signed payload 41 | inputs: 42 | targetPath: $(Build.ArtifactStagingDirectory)\layout 43 | artifactName: _osx_payload_signed 44 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/osx/pack.signed/step3-pack.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: DownloadPipelineArtifact@2 3 | displayName: Download signed payload 4 | inputs: 5 | artifact: _osx_payload_signed 6 | path: $(Build.ArtifactStagingDirectory)/layout 7 | 8 | # Pipelines artifacts do not preserve symlinks so we must reconstruct 9 | # them just before we pack the layout: 10 | # https://github.com/microsoft/azure-pipelines-tasks/issues/11980 11 | - script: | 12 | cd $(Build.ArtifactStagingDirectory)/layout/usr/local/bin 13 | rm -f scalar 14 | ln -s ../scalar/scalar . 15 | displayName: Recreate the Scalar symlink 16 | 17 | - script: Scalar.Installer.Mac/pack.sh '$(fullVersion)' '$(Build.ArtifactStagingDirectory)/layout' '$(Build.ArtifactStagingDirectory)/package' 18 | displayName: Create installer 19 | 20 | - script: rm -rf '$(Build.ArtifactStagingDirectory)/package/components' 21 | displayName: Delete component packages 22 | 23 | - task: PublishPipelineArtifact@1 24 | displayName: Publish unsigned installer 25 | inputs: 26 | targetPath: $(Build.ArtifactStagingDirectory)/package 27 | artifactName: _osx_installer_unsigned 28 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/osx/pack.signed/step4-signpack.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: ms-vseng.MicroBuildTasks.30666190-6959-11e5-9f96-f56098202fef.MicroBuildSigningPlugin@2 3 | displayName: Install signing plugin 4 | inputs: 5 | signType: $(SignType) 6 | 7 | - task: DownloadPipelineArtifact@2 8 | displayName: Download unsigned installer 9 | inputs: 10 | artifact: _osx_installer_unsigned 11 | path: $(Build.StagingDirectory)\package 12 | 13 | # Must use the NuGet & MSBuild toolchain here rather than `dotnet` 14 | # because the signing tasks target the netfx MSBuild runtime only. 15 | - task: NuGetCommand@2 16 | displayName: Restore MicroBuild packages 17 | inputs: 18 | command: restore 19 | restoreSolution: 'Scalar.Signing\Scalar.SignPackage.Mac.csproj' 20 | 21 | - task: MSBuild@1 22 | displayName: Sign installer package 23 | inputs: 24 | solution: 'Scalar.Signing\Scalar.SignPackage.Mac.csproj' 25 | msbuildArguments: '/p:RootDir="$(Build.ArtifactStagingDirectory)\package"' 26 | 27 | - task: PublishPipelineArtifact@1 28 | displayName: Publish signed installer 29 | inputs: 30 | targetPath: $(Build.ArtifactStagingDirectory)\package 31 | artifactName: _osx_installer_signed 32 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/osx/pack.signed/step5-dist.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: DownloadPipelineArtifact@2 3 | displayName: Download signed installer 4 | inputs: 5 | artifact: _osx_installer_signed 6 | path: $(Build.ArtifactStagingDirectory)/package 7 | 8 | - script: Scalar.Signing/notarize-pkg.sh -id "$(Apple.Notarization.AppleId)" -p "$(Apple.Notarization.Password)" -pkg "$(Build.ArtifactStagingDirectory)"/package/*.pkg -b "com.microsoft.scalar.pkg" 9 | displayName: Notarize installer (and containing bundles & binaries) 10 | condition: and(succeeded(), eq(variables['SignType'], 'real')) 11 | 12 | - task: DownloadPipelineArtifact@2 13 | displayName: Download Git for Mac installer package 14 | inputs: 15 | artifact: _osx_gitformac_package 16 | path: $(Build.ArtifactStagingDirectory)/gitformacpkg 17 | 18 | - script: | 19 | GCMURL=$(xmllint --xpath '//Project/PropertyGroup/GcmCoreOSXPackageUrl/text()' Directory.Build.props) || exit 1 20 | GIT_VERSION=$(grep '' Directory.Build.props | grep -Eo '[0-9.]+(-\w+)*') 21 | Scalar.Installer.Mac/dist.sh "$(Build.ArtifactStagingDirectory)/package"/Scalar*.pkg "$GIT_VERSION" "$GCMURL" "Scalar.Installer.Mac/InstallScalar.template.sh" "$(Build.ArtifactStagingDirectory)/dist" 22 | displayName: Create distribution script 23 | 24 | - task: PublishPipelineArtifact@1 25 | displayName: Publish distribution drop 26 | inputs: 27 | targetPath: $(Build.ArtifactStagingDirectory)/dist 28 | artifactName: Installers_macOS_$(configuration) 29 | 30 | # We need to recompute the product version variables here as we're in a different job 31 | # from the initial build. We pass -SetVariablesOnly because we don't need to re-set the 32 | # build number, only re-set the version variables for use in this job, and the next step. 33 | - powershell: Scripts/CI/Set-Version.ps1 -SourceBranchCounter $(branchCounter) -SetVariablesOnly 34 | displayName: "Recompute product version variables" 35 | 36 | - task: NuGetCommand@2 37 | displayName: Create distribution package (internal) 38 | inputs: 39 | command: pack 40 | packagesToPack: 'Scalar.Installer.Mac/Installers.nuspec' 41 | packDestination: $(Build.ArtifactStagingDirectory)/nupkg 42 | buildProperties: ScalarVersion=$(fullVersion) 43 | basePath: $(Build.ArtifactStagingDirectory)/dist 44 | 45 | - task: NuGetCommand@2 46 | displayName: Push distribution package (internal) 47 | inputs: 48 | command: push 49 | packagesToPush: $(Build.ArtifactStagingDirectory)/nupkg/*.nupkg 50 | nuGetFeedType: external 51 | publishFeedCredentials: $(1ESFeedCredentials) 52 | 53 | - task: PublishPipelineArtifact@1 54 | displayName: Publish distribution package (internal) 55 | inputs: 56 | targetPath: $(Build.ArtifactStagingDirectory)/nupkg 57 | artifactName: Package_macOS_$(configuration) 58 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/osx/pack.unsigned.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: Scripts/Mac/CI/CreateFTDrop.sh $(configuration) $(Build.ArtifactStagingDirectory)/Tests 3 | displayName: Create functional tests drop 4 | 5 | - task: PublishPipelineArtifact@1 6 | displayName: Publish functional tests drop 7 | inputs: 8 | targetPath: $(Build.ArtifactStagingDirectory)/Tests 9 | artifactName: FunctionalTests_macOS_$(configuration) 10 | condition: succeeded() 11 | 12 | - script: Scripts/Mac/CI/CreateInstallerDrop.sh $(configuration) $(Build.ArtifactStagingDirectory)/Installers 13 | displayName: Create distribution drop 14 | 15 | - task: PublishPipelineArtifact@1 16 | displayName: Publish distribution drop 17 | inputs: 18 | targetPath: $(Build.ArtifactStagingDirectory)/Installers 19 | artifactName: Installers_macOS_$(configuration) 20 | condition: succeeded() 21 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/win/build-and-unit-test.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: ms-vseng.MicroBuildTasks.30666190-6959-11e5-9f96-f56098202fef.MicroBuildSigningPlugin@2 3 | displayName: Install signing plugin 4 | inputs: 5 | signType: $(SignType) 6 | condition: and(succeeded(), ne(variables['SignType'], '')) 7 | 8 | - task: UseDotNet@2 9 | displayName: Use .NET Core SDK 3.1.101 10 | inputs: 11 | packageType: sdk 12 | version: 3.1.101 13 | 14 | - script: $(Build.Repository.LocalPath)\\Scripts\\NukeBuildOutputs.bat 15 | displayName: Delete previous build outputs 16 | continueOnError: true 17 | 18 | - script: $(Build.Repository.LocalPath)\.azure-pipelines\templates\win\install-git-package.bat 19 | displayName: Setup Git installer 20 | 21 | - script: $(Build.Repository.LocalPath)\Scripts\BuildScalarForWindows.bat $(configuration) $(majorAndMinorVersion).$(revision) 22 | displayName: Build Scalar ($(configuration)) 23 | 24 | - task: PublishTestResults@2 25 | displayName: Publish unit test results 26 | inputs: 27 | testRunner: VSTest 28 | testResultsFiles: "**\\*.trx" 29 | searchFolder: $(Common.TestResultsDirectory) 30 | testRunTitle: Windows $(configuration) Unit Tests 31 | publishRunAttachments: true 32 | condition: succeededOrFailed() 33 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/win/functional-test.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | useWatchman: true 3 | 4 | steps: 5 | 6 | - task: UseDotNet@2 7 | displayName: Use .NET Core SDK 3.1.101 8 | inputs: 9 | packageType: sdk 10 | version: 3.1.101 11 | 12 | - task: DownloadPipelineArtifact@2 13 | displayName: Download functional tests drop 14 | inputs: 15 | artifact: FunctionalTests_Windows_$(configuration) 16 | Path: $(Build.BinariesDirectory)\ft 17 | 18 | - task: DownloadPipelineArtifact@2 19 | displayName: Download installers drop 20 | inputs: 21 | artifact: Installers_Windows_$(configuration) 22 | path: $(Build.BinariesDirectory)\install 23 | 24 | - ${{ if eq(parameters.useWatchman, 'true') }}: 25 | - script: $(Build.BinariesDirectory)\install\InstallScalar.bat --watchman $(Build.ArtifactStagingDirectory)\logs 26 | displayName: Install product (with watchman) 27 | 28 | - ${{ if ne(parameters.useWatchman, 'true') }}: 29 | - script: $(Build.BinariesDirectory)\install\InstallScalar.bat --no-watchman $(Build.ArtifactStagingDirectory)\logs 30 | displayName: Install product (without watchman) 31 | 32 | - script: git config --global credential.interactive never 33 | displayName: Disable interactive auth 34 | 35 | - script: $(Build.BinariesDirectory)\ft\src\Scripts\RunFunctionalTests.bat $(configuration) --full-suite --test-scalar-on-path "--trace2-output=$(Build.ArtifactStagingDirectory)\logs\trace2-event.log" 36 | displayName: Run functional tests 37 | 38 | - ${{ if eq(parameters.useWatchman, 'true') }}: 39 | - task: PublishTestResults@2 40 | displayName: Publish functional tests results 41 | inputs: 42 | testRunner: NUnit 43 | testResultsFiles: "**\\TestResult*.xml" 44 | searchFolder: $(System.DefaultWorkingDirectory) 45 | testRunTitle: Windows $(configuration) Functional Tests (with watchman) 46 | publishRunAttachments: true 47 | condition: succeededOrFailed() 48 | 49 | - ${{ if eq(parameters.useWatchman, 'true') }}: 50 | - task: PublishPipelineArtifact@1 51 | displayName: Publish test and installation logs 52 | inputs: 53 | targetPath: $(Build.ArtifactStagingDirectory)\logs 54 | artifactName: Logs_Windows_Watchman 55 | condition: failed() 56 | 57 | - ${{ if ne(parameters.useWatchman, 'true') }}: 58 | - task: PublishTestResults@2 59 | displayName: Publish functional tests results 60 | inputs: 61 | testRunner: NUnit 62 | testResultsFiles: "**\\TestResult*.xml" 63 | searchFolder: $(System.DefaultWorkingDirectory) 64 | testRunTitle: Windows $(configuration) Functional Tests (without watchman) 65 | publishRunAttachments: true 66 | condition: succeededOrFailed() 67 | 68 | - ${{ if ne(parameters.useWatchman, 'true') }}: 69 | - task: PublishPipelineArtifact@1 70 | displayName: Publish test and installation logs 71 | inputs: 72 | targetPath: $(Build.ArtifactStagingDirectory)\logs 73 | artifactName: Logs_Windows 74 | condition: failed() 75 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/win/install-git-package.bat: -------------------------------------------------------------------------------- 1 | "C:\Program Files\Git\bin\bash.exe" .azure-pipelines/templates/win/install-git-package.sh 2 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/win/install-git-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GIT_VERSION=$(grep '' Directory.Build.props | grep -Eo '[0-9.]+(-\w+)*') 4 | mkdir ../out 5 | cd ../out 6 | dotnet new classlib -n Scalar.GitInstaller 7 | cd Scalar.GitInstaller 8 | dotnet add Scalar.GitInstaller.csproj package "GitForWindows.GVFS.Installer" --package-directory . --version "$GIT_VERSION" --source "https://pkgs.dev.azure.com/gvfs/ci/_packaging/Dependencies/nuget/v3/index.json" 9 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/win/pack.signed.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | # Must use the NuGet & MSBuild toolchain here rather than `dotnet` 3 | # because the signing tasks target the netfx MSBuild runtime only. 4 | - task: NuGetCommand@2 5 | displayName: Restore MicroBuild packages 6 | inputs: 7 | command: restore 8 | restoreSolution: 'Scalar.Signing\Scalar.SignFiles.Windows.csproj' 9 | 10 | - task: NuGetCommand@2 11 | displayName: Restore MicroBuild packages 12 | inputs: 13 | command: restore 14 | restoreSolution: 'Scalar.Signing\Scalar.SignInstaller.Windows.csproj' 15 | 16 | - task: MSBuild@1 17 | displayName: Collect payload files 18 | inputs: 19 | solution: 'Scalar.Installer.Windows\Scalar.Installer.Windows.csproj' 20 | configuration: $(configuration) 21 | msbuildArguments: '/t:BuildInstallerPhase1 /p:LayoutPath="$(Build.ArtifactStagingDirectory)\signpayload" /p:ScalarVersion=$(majorAndMinorVersion).$(revision)' 22 | 23 | - task: MSBuild@1 24 | displayName: Sign payload 25 | inputs: 26 | solution: 'Scalar.Signing\Scalar.SignFiles.Windows.csproj' 27 | msbuildArguments: '/p:RootDir="$(Build.ArtifactStagingDirectory)\signpayload"' 28 | 29 | - task: MSBuild@1 30 | displayName: Build installer 31 | inputs: 32 | solution: 'Scalar.Installer.Windows\Scalar.Installer.Windows.csproj' 33 | configuration: $(configuration) 34 | msbuildArguments: '/t:BuildInstallerPhase2 /p:LayoutPath="$(Build.ArtifactStagingDirectory)\signpayload" /p:InstallerOutputPath="$(Build.ArtifactStagingDirectory)\signinstaller" /p:ScalarVersion=$(majorAndMinorVersion).$(revision)' 35 | 36 | - task: MSBuild@1 37 | displayName: Sign installer 38 | inputs: 39 | solution: 'Scalar.Signing\Scalar.SignInstaller.Windows.csproj' 40 | msbuildArguments: '/p:RootDir="$(Build.ArtifactStagingDirectory)\signinstaller"' 41 | 42 | - script: rmdir /s /q ..\out\Scalar.Installer.Windows\dist 43 | displayName: Clean distribution output directory 44 | 45 | - task: MSBuild@1 46 | displayName: Create distribution 47 | inputs: 48 | solution: 'Scalar.Installer.Windows\Scalar.Installer.Windows.csproj' 49 | configuration: $(configuration) 50 | msbuildArguments: '/t:BuildInstallerPhase3 /p:InstallerOutputPath="$(Build.ArtifactStagingDirectory)\signinstaller"' 51 | 52 | - script: Scripts\CI\CreateFTDrop.bat $(configuration) $(Build.ArtifactStagingDirectory)\Tests 53 | displayName: Create functional tests drop 54 | 55 | - task: PublishPipelineArtifact@1 56 | displayName: Publish functional tests drop 57 | inputs: 58 | targetPath: $(Build.ArtifactStagingDirectory)\Tests\ 59 | artifactName: "FunctionalTests_Windows_$(configuration)" 60 | condition: succeeded() 61 | 62 | - script: Scripts\CI\CreateInstallerDrop.bat $(configuration) $(Build.ArtifactStagingDirectory)\Installers 63 | displayName: Create distribution drop 64 | 65 | - task: PublishPipelineArtifact@1 66 | displayName: Publish distribution drop 67 | inputs: 68 | targetPath: $(Build.ArtifactStagingDirectory)\Installers\ 69 | artifactName: "Installers_Windows_$(configuration)" 70 | condition: succeeded() 71 | 72 | - task: NuGetCommand@2 73 | displayName: Create distribution package (internal) 74 | inputs: 75 | command: pack 76 | packagesToPack: 'Scalar.Installer.Windows\Installers.nuspec' 77 | packDestination: $(Build.ArtifactStagingDirectory)\NuGetPackage 78 | buildProperties: ScalarVersion=$(fullVersion) 79 | basePath: ..\out\Scalar.Installer.Windows\dist\$(configuration) 80 | 81 | - task: NuGetCommand@2 82 | displayName: Push distribution package (internal) 83 | inputs: 84 | command: push 85 | packagesToPush: $(Build.ArtifactStagingDirectory)\NuGetPackage\*.nupkg 86 | nuGetFeedType: external 87 | publishFeedCredentials: $(1ESFeedCredentials) 88 | 89 | - task: PublishPipelineArtifact@1 90 | displayName: Publish distribution package (internal) 91 | inputs: 92 | targetPath: $(Build.ArtifactStagingDirectory)\NuGetPackage\ 93 | artifactName: "Package_Windows_$(configuration)" 94 | condition: succeeded() 95 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/win/pack.unsigned.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: Scripts\CI\CreateFTDrop.bat $(configuration) $(Build.ArtifactStagingDirectory)\Tests 3 | displayName: Create functional tests drop 4 | 5 | - task: PublishPipelineArtifact@1 6 | displayName: Publish functional tests drop 7 | inputs: 8 | targetPath: $(Build.ArtifactStagingDirectory)\Tests\ 9 | artifactName: "FunctionalTests_Windows_$(configuration)" 10 | condition: succeeded() 11 | 12 | - script: Scripts\CI\CreateInstallerDrop.bat $(configuration) $(Build.ArtifactStagingDirectory)\Installers 13 | displayName: Create distribution drop 14 | 15 | - task: PublishPipelineArtifact@1 16 | displayName: Publish distribution drop 17 | inputs: 18 | targetPath: $(Build.ArtifactStagingDirectory)\Installers\ 19 | artifactName: "Installers_Windows_$(configuration)" 20 | condition: succeeded() 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | 10 | [*.{cs,cpp,h}] 11 | indent_style = space 12 | indent_size = 4 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Do not normalize any line endings. 3 | ############################################################################### 4 | * -text 5 | *.cs diff=csharp 6 | 7 | -------------------------------------------------------------------------------- /.github/run_esrp_signing.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import glob 4 | import pprint 5 | import subprocess 6 | import sys 7 | 8 | esrp_tool = os.path.join("esrp", "tools", "EsrpClient.exe") 9 | 10 | aad_id = os.environ['AZURE_AAD_ID'].strip() 11 | workspace = os.environ['GITHUB_WORKSPACE'].strip() 12 | 13 | source_root_location = os.path.join(workspace, "tosign") 14 | destination_location = os.path.join(workspace) 15 | 16 | scalar_files = glob.glob(os.path.join(source_root_location, "scalar-linux*.deb")) 17 | azrepos_files = glob.glob(os.path.join(source_root_location, "scalar-azrepos-linux*.deb")) 18 | 19 | print("Found files:") 20 | pprint.pp(scalar_files) 21 | pprint.pp(azrepos_files) 22 | 23 | if len(scalar_files) < 1 or not scalar_files[0].endswith(".deb"): 24 | print("Error: cannot find scalar .deb to sign") 25 | exit(1) 26 | 27 | if len(azrepos_files) < 1 or not azrepos_files[0].endswith(".deb"): 28 | print("Error: cannot find scalar-azrepos .deb to sign") 29 | exit(1) 30 | 31 | scalar_to_sign = os.path.basename(scalar_files[0]) 32 | azrepos_to_sign = os.path.basename(azrepos_files[0]) 33 | 34 | auth_json = { 35 | "Version": "1.0.0", 36 | "AuthenticationType": "AAD_CERT", 37 | "TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", 38 | "ClientId": aad_id, 39 | "AuthCert": { 40 | "SubjectName": f"CN={aad_id}.microsoft.com", 41 | "StoreLocation": "LocalMachine", 42 | "StoreName": "My", 43 | }, 44 | "RequestSigningCert": { 45 | "SubjectName": f"CN={aad_id}", 46 | "StoreLocation": "LocalMachine", 47 | "StoreName": "My", 48 | } 49 | } 50 | 51 | input_json = { 52 | "Version": "1.0.0", 53 | "SignBatches": [ 54 | { 55 | "SourceLocationType": "UNC", 56 | "SourceRootDirectory": source_root_location, 57 | "DestinationLocationType": "UNC", 58 | "DestinationRootDirectory": destination_location, 59 | "SignRequestFiles": [ 60 | { 61 | "CustomerCorrelationId": "01A7F55F-6CDD-4123-B255-77E6F212CDAD", 62 | "SourceLocation": scalar_to_sign, 63 | "DestinationLocation": os.path.join("signed", scalar_to_sign), 64 | }, 65 | { 66 | "CustomerCorrelationId": "01A7F55F-6CDD-4123-B255-77E6F212CDAD", 67 | "SourceLocation": azrepos_to_sign, 68 | "DestinationLocation": os.path.join("signed", azrepos_to_sign), 69 | } 70 | ], 71 | "SigningInfo": { 72 | "Operations": [ 73 | { 74 | "KeyCode": "CP-450779-Pgp", 75 | "OperationCode": "LinuxSign", 76 | "Parameters": {}, 77 | "ToolName": "sign", 78 | "ToolVersion": "1.0", 79 | } 80 | ] 81 | } 82 | } 83 | ] 84 | } 85 | 86 | policy_json = { 87 | "Version": "1.0.0", 88 | "Intent": "production release", 89 | "ContentType": "Debian package", 90 | } 91 | 92 | configs = [ 93 | ("auth.json", auth_json), 94 | ("input.json", input_json), 95 | ("policy.json", policy_json), 96 | ] 97 | 98 | for filename, data in configs: 99 | with open(filename, 'w') as fp: 100 | json.dump(data, fp) 101 | 102 | # Run ESRP Client 103 | esrp_out = "esrp_out.json" 104 | result = subprocess.run( 105 | [esrp_tool, "sign", 106 | "-a", "auth.json", 107 | "-i", "input.json", 108 | "-p", "policy.json", 109 | "-o", esrp_out, 110 | "-l", "Verbose"], 111 | cwd=workspace) 112 | 113 | if result.returncode != 0: 114 | print("Failed to run ESRPClient.exe") 115 | sys.exit(1) 116 | 117 | if os.path.isfile(esrp_out): 118 | print("ESRP output json:") 119 | with open(esrp_out, 'r') as fp: 120 | pprint.pp(json.load(fp)) 121 | 122 | scalar_signed = os.path.join(destination_location, "signed", scalar_to_sign) 123 | if os.path.isfile(scalar_signed): 124 | print(f"Success!\nSigned {scalar_signed}") 125 | 126 | azrepos_to_sign = os.path.join(destination_location, "signed", azrepos_to_sign) 127 | if os.path.isfile(azrepos_to_sign): 128 | print(f"Success!\nSigned {azrepos_to_sign}") 129 | -------------------------------------------------------------------------------- /.github/workflows/build-signed-deb.yml: -------------------------------------------------------------------------------- 1 | name: "Build Signed Debian Packages" 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [released] 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-18.04 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Setup .NET Core 16 | uses: actions/setup-dotnet@v1 17 | with: 18 | dotnet-version: 3.1.302 19 | 20 | - name: Install dependencies 21 | run: dotnet restore --force 22 | 23 | - name: Build Linux packages 24 | run: | 25 | BRANCH=$(git branch --show-current) 26 | if [ "${BRANCH:0:9}" = "releases/" ]; then 27 | SCALARVERSION="${BRANCH:9}".0 28 | else 29 | SCALARVERSION=0.3.132.0 30 | fi 31 | echo $SCALARVERSION 32 | dotnet publish -c Release -p:ScalarVersion=$SCALARVERSION 'Scalar.Packaging.Linux/Scalar.Packaging.Linux.csproj' 33 | 34 | # Because the actions/upload-artifact action does not allow you to specify 35 | # relative file paths we must first use a shell script to copy the 36 | # artifacts to a different directory. 37 | - name: Collect packages 38 | shell: bash 39 | run: | 40 | rm -rf to_upload 41 | mkdir to_upload 42 | cp ../out/Scalar.Packaging.Linux/deb/Release/*.deb to_upload 43 | 44 | - name: Upload packages 45 | uses: actions/upload-artifact@v2 46 | with: 47 | name: DebianUnsigned 48 | path: to_upload/* 49 | 50 | sign: 51 | name: Sign 52 | runs-on: windows-latest 53 | needs: build 54 | steps: 55 | - name: setup python 56 | uses: actions/setup-python@v2 57 | with: 58 | python-version: 3.8 59 | 60 | - uses: actions/checkout@v2 61 | 62 | - name: 'Download Unsigned Packages' 63 | uses: actions/download-artifact@v4.1.7 64 | with: 65 | name: DebianUnsigned 66 | path: tosign 67 | 68 | - uses: Azure/login@v1.1 69 | with: 70 | creds: ${{ secrets.AZURE_CREDENTIALS }} 71 | 72 | - name: 'Install ESRP Client' 73 | shell: pwsh 74 | env: 75 | AZ_SUB: ${{ secrets.AZURE_SUBSCRIPTION }} 76 | run: | 77 | az storage blob download --subscription "$env:AZ_SUB" --account-name gitcitoolstore -c tools -n microsoft.esrpclient.1.2.47.nupkg -f esrp.zip 78 | Expand-Archive -Path esrp.zip -DestinationPath .\esrp 79 | 80 | - name: Install Certificates 81 | shell: pwsh 82 | env: 83 | AZ_SUB: ${{ secrets.AZURE_SUBSCRIPTION }} 84 | AZ_VAULT: ${{ secrets.AZURE_VAULT }} 85 | SSL_CERT: ${{ secrets.VAULT_SSL_CERT_NAME }} 86 | ESRP_CERT: ${{ secrets.VAULT_ESRP_CERT_NAME }} 87 | run: | 88 | az keyvault secret download --subscription "$env:AZ_SUB" --vault-name "$env:AZ_VAULT" --name "$env:SSL_CERT" -f out.pfx 89 | certutil -f -importpfx out.pfx 90 | Remove-Item out.pfx 91 | 92 | az keyvault secret download --subscription "$env:AZ_SUB" --vault-name "$env:AZ_VAULT" --name "$env:ESRP_CERT" -f out.pfx 93 | certutil -f -importpfx out.pfx 94 | Remove-Item out.pfx 95 | 96 | - name: Run ESRP Client 97 | shell: pwsh 98 | env: 99 | AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }} 100 | run: | 101 | python .github/run_esrp_signing.py 102 | 103 | - name: Upload Signed Packages 104 | uses: actions/upload-artifact@v2 105 | with: 106 | name: DebianSigned 107 | path: | 108 | signed/*.deb 109 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Scalar 2 | 3 | on: 4 | push: 5 | branches: [ main, releases/* ] 6 | pull_request: 7 | branches: [ main, releases/* ] 8 | 9 | env: 10 | SCALAR_TEST_SKIP_VSTS_INFO: true 11 | 12 | jobs: 13 | validate_scalar: 14 | name: "CI" 15 | 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [ ubuntu-20.04, windows-2019, macos-13] 21 | features: [ignored] 22 | 23 | env: 24 | BUILD_FRAGMENT: bin/Release/netcoreapp3.1 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | with: 29 | fetch-depth: 0 # Indicate full history so Nerdbank.GitVersioning works. 30 | 31 | - name: Setup .NET Core 32 | uses: actions/setup-dotnet@v1 33 | with: 34 | dotnet-version: 3.1.302 35 | 36 | - name: Install dependencies 37 | run: dotnet restore 38 | env: 39 | DOTNET_NOLOGO: 1 40 | 41 | - name: Build 42 | run: dotnet build --configuration Release --no-restore -p:UseAppHost=true # Force generation of executable on macOS. 43 | 44 | - name: Unit test 45 | run: dotnet test --no-restore 46 | -------------------------------------------------------------------------------- /.github/workflows/homebrew-release.yml: -------------------------------------------------------------------------------- 1 | name: Update Homebrew Tap 2 | on: 3 | release: 4 | types: [released] 5 | 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - id: version 11 | name: Compute version number 12 | run: | 13 | echo "result=$(echo $GITHUB_REF | sed -e "s/^refs\/tags\/v//")" >> $GITHUB_OUTPUT 14 | - id: hash 15 | name: Compute release asset hash 16 | uses: mjcheetham/asset-hash@v1 17 | with: 18 | asset: Installers_macOS_Release.zip 19 | hash: sha256 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | - name: Update scalar Cask 22 | uses: mjcheetham/update-homebrew@v1.1 23 | with: 24 | token: ${{ secrets.HOMEBREW_TOKEN }} 25 | tap: microsoft/git 26 | name: scalar 27 | type: cask 28 | version: ${{ steps.version.outputs.result }} 29 | sha256: ${{ steps.hash.outputs.result }} 30 | alwaysUsePullRequest: true 31 | - name: Update scalar-azrepos Cask 32 | uses: mjcheetham/update-homebrew@v1.1 33 | with: 34 | token: ${{ secrets.HOMEBREW_TOKEN }} 35 | tap: microsoft/git 36 | name: scalar-azrepos 37 | type: cask 38 | version: ${{ steps.version.outputs.result }} 39 | sha256: ${{ steps.hash.outputs.result }} 40 | alwaysUsePullRequest: true 41 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 * * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v1 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'Labeling this issue as stale. There has been no activity for 30 days. Remove stale label or comment or this issue will be closed in 7 days.' 17 | stale-pr-message: 'Labeling this pull request as stale. There has been no activity for 30 days. Remove stale label or comment or this PR will be closed in 7 days.' 18 | stale-issue-label: 'no-activity' 19 | stale-pr-label: 'no-activity' 20 | days-before-stale: 90 21 | days-before-close: 7 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # VS 2017 user-specific files 5 | launchSettings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # Mac 14 | xcuserdata 15 | .DS_Store 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | build/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | 32 | # Visual Studio 2015 cache/options directory 33 | .vs/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult*.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # DNX 49 | project.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.opendb 88 | *.VC.db 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding add-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | ## TODO: Comment the next line if you want to checkin your 146 | ## web deploy settings but do note that will include unencrypted 147 | ## passwords 148 | #*.pubxml 149 | 150 | *.publishproj 151 | 152 | # NuGet Packages 153 | *.nupkg 154 | # The packages folder can be ignored because of Package Restore 155 | **/packages/* 156 | # except build/, which is used as an MSBuild target. 157 | !**/packages/build/ 158 | # Uncomment if necessary however generally it will be regenerated when needed 159 | #!**/packages/repositories.config 160 | 161 | # Windows Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Windows Store app package directory 166 | AppPackages/ 167 | 168 | # Visual Studio cache files 169 | # files ending in .cache can be ignored 170 | *.[Cc]ache 171 | # but keep track of directories ending in .cache 172 | !*.[Cc]ache/ 173 | 174 | # Others 175 | ClientBin/ 176 | [Ss]tyle[Cc]op.* 177 | ~$* 178 | *~ 179 | *.dbmdl 180 | *.dbproj.schemaview 181 | *.pfx 182 | *.publishsettings 183 | node_modules/ 184 | orleans.codegen.cs 185 | 186 | # RIA/Silverlight projects 187 | Generated_Code/ 188 | 189 | # Backup & report files from converting an old project file 190 | # to a newer Visual Studio version. Backup files are not needed, 191 | # because we have git ;-) 192 | _UpgradeReport_Files/ 193 | Backup*/ 194 | UpgradeLog*.XML 195 | UpgradeLog*.htm 196 | 197 | # SQL Server files 198 | *.mdf 199 | *.ldf 200 | 201 | # Business Intelligence projects 202 | *.rdl.data 203 | *.bim.layout 204 | *.bim_*.settings 205 | 206 | # Microsoft Fakes 207 | FakesAssemblies/ 208 | 209 | # Node.js Tools for Visual Studio 210 | .ntvs_analysis.dat 211 | 212 | # Visual Studio 6 build log 213 | *.plg 214 | 215 | # Visual Studio 6 workspace options file 216 | *.opt 217 | 218 | # LightSwitch generated files 219 | GeneratedArtifacts/ 220 | _Pvt_Extensions/ 221 | ModelManifest.xml 222 | 223 | *.dll 224 | *.cab 225 | *.cer 226 | 227 | # VS Code private directory 228 | .vscode/ 229 | 230 | # JetBrains IDEs 231 | .idea/ 232 | -------------------------------------------------------------------------------- /.vsconfig: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "components": [ 4 | "Microsoft.Component.MSBuild", 5 | "Microsoft.VisualStudio.Workload.NativeDesktop" 6 | "Microsoft.VisualStudio.Workload.ManagedDesktop", 7 | "Microsoft.VisualStudio.Workload.NetCoreTools", 8 | "Microsoft.Net.Core.Component.SDK.2.1", 9 | "Microsoft.VisualStudio.Component.VC.v141.x86.x64", 10 | "Microsoft.Net.Component.4.6.1.TargetingPack", 11 | "Microsoft.Net.Component.4.6.1.SDK", 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /AuthoringTests.md: -------------------------------------------------------------------------------- 1 | # Authoring Tests 2 | 3 | ## Functional Tests 4 | 5 | #### Runnable functional test projects 6 | 7 | - `Scalar.FunctionalTests` 8 | 9 | `Scalar.FunctionalTests` is a .NET Core project and contains all cross-platform functional tests. 10 | 11 | ## Running the Functional Tests 12 | 13 | The functional tests are built on NUnit 3, which is available as a set of NuGet packages. 14 | 15 | ### Windows 16 | 17 | 1. Build Scalar: 18 | 19 | **Option 1:** Open Scalar.sln in Visual Studio and build everything. 20 | 21 | **Option 2:** Run `Scripts\BuildScalarForWindows.bat` from the command line 22 | 23 | 2. Run the Scalar installer that was built in step 2. This will ensure that Scalar will be able to find the correct version of the pre/post-command hooks. The installer will be placed in `BuildOutput\Scalar.Installer.Windows\bin\x64\` 24 | 3. Run the tests **with elevation**. Elevation is required because the functional tests create and delete a test service. 25 | 26 | **Option 1:** Run the `Scalar.FunctionalTests` project from inside Visual Studio launched as Administrator. 27 | 28 | **Option 2:** Run `Scripts\RunFunctionalTests.bat` from CMD launched as Administrator. 29 | 30 | #### Selecting Which Tests are Run 31 | 32 | By default, the functional tests run on a single configuration. Passing the `--full-suite` option runs all tests on all configurations. 33 | 34 | ### Mac 35 | 36 | 1. Build Scalar: `Scripts/Mac/BuildScalarForMac.sh` 37 | 2. Run the tests: `Scripts/Mac/RunFunctionalTests.sh ` 38 | 39 | If you need the VS for Mac debugger attached for a functional test run: 40 | 41 | 1. Make sure you've built your latest changes 42 | 2. Open Scalar.sln in VS for Mac 43 | 3. Run->Run With->Custom Configuration... 44 | 4. Select "Start external program" and specify the published functional test binary (e.g. `/Users//Repos/Scalar/Publish/Scalar.FunctionalTests`) 45 | 5. Specify any desired arguments (e.g. [a specific test](#Running-Specific-Tests) ) 46 | 6. Run Action -> "Debug - .Net Core Debugger" 47 | 7. Click "Debug" 48 | 49 | ### Customizing the Functional Test Settings 50 | 51 | The functional tests take a set of parameters that indicate what paths and URLs to work with. If you want to customize those settings, they 52 | can be found in [`Scalar.FunctionalTests\Settings.cs`](/Scalar/Scalar.FunctionalTests/Settings.cs). 53 | 54 | 55 | ## Running Specific Tests 56 | 57 | Specific tests can be run by adding the `--test=` command line argument to the functional test project/scripts. 58 | 59 | Note that the test name must include the class and namespace and that `Debug` or `Release` must be specified when running the functional test scripts. 60 | 61 | *Example* 62 | 63 | Windows (Script): 64 | 65 | `Scripts\RunFunctionalTests.bat Debug --test=Scalar.FunctionalTests.Tests.EnlistmentPerFixture.CloneTests.CloneToPathWithSpaces 66 | 67 | Windows (Visual Studio): 68 | 69 | 1. Set `Scalar.FunctionalTests` as StartUp project 70 | 2. Project Properties->Debug->Start options->Command line arguments (all on a single line): `--test=Scalar.FunctionalTests.Tests.EnlistmentPerFixture.CloneTests.CloneToPathWithSpaces 71 | 72 | Mac: 73 | 74 | `Scripts/Mac/RunFunctionalTests.sh Debug --test=Scalar.FunctionalTests.Tests.EnlistmentPerFixture.CloneTests.CloneToPathWithSpaces` 75 | 76 | ## How to Write a Functional Test 77 | 78 | Each piece of functionality that we add to Scalar should have corresponding functional tests that clone a repo and use existing tools and filesystem APIs to interact with the virtual repo. 79 | 80 | Since these are functional tests that can potentially modify the state of files on disk, you need to be careful to make sure each test can run in a clean 81 | environment. There are two base classes that you can derive from when writing your tests. It's also important to put your new class into the same namespace 82 | as the base class, because NUnit treats namespaces like test suites, and we have logic that keys off that for deciding when to create enlistments. 83 | 84 | 1. `TestsWithEnlistmentPerFixture` 85 | 86 | For any test fixture (a fixture is the same as a class in NUnit) that derives from this class, we create an enlistment before running any of the tests in the fixture, and then we delete the enlistment after all tests are done (but before any other fixture runs). If you need to write a sequence of tests that manipulate the same repo, this is the right base class. 87 | 88 | 2. `TestsWithEnlistmentPerTestCase` 89 | 90 | Derive from this class if you need a new enlistment created for each test case. This is the most reliable, but also most expensive option. 91 | 92 | ## Updating the Remote Test Branch 93 | 94 | By default, the functional tests clone `main`, check out the branch "FunctionalTests/YYYYMMDD" (with the day the FunctionalTests branch was created), 95 | and then remove all remote tracking information. This is done to guarantee that remote changes to tip cannot break functional tests. If you need to update 96 | the functional tests to use a new FunctionalTests branch, you'll need to create a new "FunctionalTests/YYYYMMDD" branch and update the `Commitish` setting in `Settings.cs` to have this new branch name. 97 | Once you have verified your scenarios locally you can push the new FunctionalTests branch and then your changes. 98 | -------------------------------------------------------------------------------- /Dependencies.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | windows 8 | osx 9 | linux 10 | true 11 | false 12 | 13 | 14 | Debug 15 | 16 | 17 | $(MSBuildThisFileDirectory) 18 | $(RepoPath) 19 | $(RepoPath)..\out\ 20 | $(RepoSrcPath)Scalar.MSBuild\ 21 | 22 | 23 | $(RepoOutPath)$(MSBuildProjectName)\ 24 | $(ProjectOutPath)bin\ 25 | $(ProjectOutPath)obj\ 26 | 27 | 28 | win10-x64;osx-x64;linux-x64 29 | true 30 | true 31 | $(RepoPath)Scalar.ruleset 32 | 33 | 34 | Scalar 35 | Microsoft Corporation (c) 36 | 37 | 38 | 0.2.173.2 39 | 40 | 46 | 2.20211031.5-pr 47 | v2.25.0.vfs.1.1 48 | 49 | https://github.com/facebook/watchman/releases/download/v2020.08.03.00/watchman-v2020.08.03.00-windows.zip 50 | https://github.com/microsoft/Git-Credential-Manager-Core/releases/download/v2.0.79-beta/gcmcore-osx-2.0.79.64449.pkg 51 | 52 | 53 | Microsoft400 54 | 8003 55 | 56 | 57 | extbin 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | $(AuthenticodeCert) 69 | false 70 | 71 | 72 | false 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(ScalarVersion) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Images/scalar-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/scalar/1f66ccf37864e25fc27c0b786b425eb2695f814e/Images/scalar-card.png -------------------------------------------------------------------------------- /Images/scalar-card.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 64 | 70 | Scalar 81 | 82 | 83 | -------------------------------------------------------------------------------- /Images/scalar-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/scalar/1f66ccf37864e25fc27c0b786b425eb2695f814e/Images/scalar-large.png -------------------------------------------------------------------------------- /Images/scalar.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/scalar/1f66ccf37864e25fc27c0b786b425eb2695f814e/Images/scalar.ico -------------------------------------------------------------------------------- /Images/scalar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 64 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /Protocol.md: -------------------------------------------------------------------------------- 1 | See Protocol.md in microsoft/vfsforgit. 2 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ![Scalar](Images/scalar-card.png) 2 | 3 | ## What is Scalar? 4 | 5 | Scalar is a tool that helps Git scale to some of the largest Git repositories. 6 | It achieves this by enabling some advanced Git features, such as: 7 | 8 | * *Partial clone:* reduces time to get a working repository by not 9 | downloading all Git objects right away. 10 | 11 | * *Background prefetch:* downloads Git object data from all remotes every 12 | hour, reducing the amount of time for foreground `git fetch` calls. 13 | 14 | * *Sparse-checkout:* limits the size of your working directory. 15 | 16 | * *File system monitor:* tracks the recently modified files and eliminates 17 | the need for Git to scan the entire worktree. 18 | 19 | * *Commit-graph:* accelerates commit walks and reachability calculations, 20 | speeding up commands like `git log`. 21 | 22 | * *Multi-pack-index:* enables fast object lookups across many pack-files. 23 | 24 | * *Incremental repack:* Repacks the packed Git data into fewer pack-file 25 | without disrupting concurrent commands by using the multi-pack-index. 26 | 27 | As new versions of Git are released, we update the list of features that 28 | Scalar automatically configures. This reduces your effort to keep your 29 | repositories as efficient as possible. 30 | 31 | ## Scalar has moved! 32 | 33 | Through significant effort from our team, we have successfully transitioned 34 | Scalar from a modified version of [VFS for Git][vfsforgit] into a thin shell 35 | around core Git features. The Scalar executable has now been ported to be 36 | included [in the `microsoft/git` fork][microsoft-git]. Please visit that 37 | fork for all of your Scalar needs: 38 | 39 | * Download [the latest `microsoft/git` release][releases]. 40 | * Read [the Scalar documentation][docs]. 41 | * Contribute changes [to the `scalar` CLI][scalar-cli]. 42 | 43 | ### Why did Scalar move? 44 | 45 | Scalar started as a modification of [VFS for Git][vfsforgit] to 46 | create a working solution with a robust test suite in a short amount of 47 | time. The goal was to depend more on features that exist within Git itself 48 | instead of creating new functionality within this project. Since the start, 49 | we have focused on this goal with efforts such as 50 | [improving sparse-checkout performance in Git][sparse-checkout-blog], 51 | [implementing background maintenance in Git][background-maintenance], 52 | and [integrating the GVFS protocol into `microsoft/git`][remove-read-object] 53 | which allowed us to [drop the `Scalar.Mount` process][remove-mount]. 54 | All of these changes reduced the size of the code in Scalar itself until 55 | it could be replaced [with a small command-line interface][scalar-cli]. 56 | 57 | [sparse-checkout-blog]: https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/ 58 | [background-maintenance]: https://github.blog/2021-03-15-highlights-from-git-2-31/#introducing-git-maintenance 59 | [remove-read-object]: https://github.com/microsoft/scalar/pull/122 60 | [remove-mount]: https://github.com/microsoft/scalar/pull/222 61 | 62 | Additional benefits to this change include making our release and 63 | installation mechanism much simpler. Users now only need to install one 64 | tool, not multiple, to take advantage of all of the benefits. 65 | 66 | ### What remains in this repository? 67 | 68 | We are keeping the `microsoft/scalar` repository available since we have 69 | linked to it and want to make sure those links continue to work. We 70 | added pointers in several places to navigate readers to the `microsoft/git` 71 | repository for the latest versions. 72 | 73 | We also have a large set of functional tests that verify that Scalar 74 | enlistments continue to work in a variety of advanced Git scenarios. These 75 | tests are incredibly helpful as we advance features in `microsoft/git`, so 76 | those tests remain in this repository. We run them as part of pull request 77 | validation in `microsoft/git`, so no changes are made there without passing 78 | this suite of tests. 79 | 80 | ### What if I already installed Scalar and want the new version? 81 | 82 | We are working to ensure that users on the .NET version of Scalar have a 83 | painless experience while changing to the new version. 84 | 85 | * On Windows, users can [install `microsoft/git`][windows-install] and the 86 | installer will remove the .NET version and update any registered 87 | enlistments to work with the new version. 88 | 89 | * On macOS, users should run `brew uninstall --cask scalar` or 90 | `brew uninstall --cask scalar-azrepos` depending on their version and 91 | then run `brew install --cask microsoft-git` to get the new version. 92 | At the moment, users on macOS will need to re-run `scalar register` 93 | on their enlistments to ensure they are registered for future upgrades. 94 | 95 | * On Linux, there is no established uninstall mechanism, but the .NET 96 | version can be removed via `sudo rm -rf /usr/local/lib/scalar/`. Installing 97 | the new version will overwrite the `scalar` binary in `/usr/local/bin`. 98 | At the moment, users on Linux will need to re-run `scalar register` 99 | on their enlistments to ensure they are registered for future upgrades. 100 | 101 | You can check if the new Scalar version is installed correctly by running 102 | `scalar version` which should have the same output as `git version`. 103 | 104 | ## License 105 | 106 | The Scalar source code in this repo is available under the MIT license. See [License.md](License.md). 107 | 108 | ## Code of Conduct 109 | 110 | This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code]. 111 | For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments. 112 | 113 | [vfsforgit]: https://github.com/microsoft/vfsforgit 114 | [microsoft-git]: https://github.com/microsoft/git 115 | [releases]: https://github.com/microsoft/git/releases 116 | [windows-install]: https://github.com/microsoft/git#windows 117 | [docs]: https://github.com/microsoft/git/blob/HEAD/contrib/scalar/docs/index.md 118 | [scalar-cli]: https://github.com/microsoft/git/blob/HEAD/contrib/scalar/scalar.c 119 | [conduct-code]: https://opensource.microsoft.com/codeofconduct/ 120 | [conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/ 121 | [conduct-email]: mailto:opencode@microsoft.com 122 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 4 | 5 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. 6 | 7 | ## Reporting Security Issues 8 | 9 | **Please do not report security vulnerabilities through public GitHub issues.** 10 | 11 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 12 | 13 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 14 | 15 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 16 | 17 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 18 | 19 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 30 | 31 | ## Preferred Languages 32 | 33 | We prefer all communications to be in English. 34 | 35 | ## Policy 36 | 37 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). -------------------------------------------------------------------------------- /Scalar.FunctionalTests/AssemblyAttributes.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | [assembly: Parallelizable(ParallelScope.Fixtures)] 4 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Categories.cs: -------------------------------------------------------------------------------- 1 | namespace Scalar.FunctionalTests 2 | { 3 | public static class Categories 4 | { 5 | public const string GitCommands = "GitCommands"; 6 | public const string Maintenance = "Maintenance"; 7 | 8 | public const string WindowsOnly = "WindowsOnly"; 9 | public const string POSIXOnly = "POSIXOnly"; 10 | 11 | public const string GitRepository = "GitRepository"; 12 | 13 | public const string NeedsUpdatesForNonVirtualizedMode = "NeedsUpdatesForNonVirtualizedMode"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/FileSystemRunners/FileSystemRunner.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | 4 | namespace Scalar.FunctionalTests.FileSystemRunners 5 | { 6 | public abstract class FileSystemRunner 7 | { 8 | private static FileSystemRunner defaultRunner = new SystemIORunner(); 9 | 10 | public static object[] AllWindowsRunners { get; } = 11 | new[] 12 | { 13 | new object[] { new SystemIORunner() }, 14 | new object[] { new CmdRunner() }, 15 | new object[] { new PowerShellRunner() }, 16 | new object[] { new BashRunner() }, 17 | }; 18 | 19 | public static object[] AllPOSIXRunners { get; } = 20 | new[] 21 | { 22 | new object[] { new SystemIORunner() }, 23 | new object[] { new BashRunner() }, 24 | }; 25 | 26 | public static object[] DefaultRunners { get; } = 27 | new[] 28 | { 29 | new object[] { defaultRunner } 30 | }; 31 | 32 | public static object[] Runners 33 | { 34 | get { return ScalarTestConfig.FileSystemRunners; } 35 | } 36 | 37 | /// 38 | /// Default runner to use (for tests that do not need to be run with multiple runners) 39 | /// 40 | public static FileSystemRunner DefaultRunner 41 | { 42 | get { return defaultRunner; } 43 | } 44 | 45 | // File methods 46 | public abstract bool FileExists(string path); 47 | public abstract string MoveFile(string sourcePath, string targetPath); 48 | 49 | public abstract string ReplaceFile(string sourcePath, string targetPath); 50 | public abstract string DeleteFile(string path); 51 | public abstract string ReadAllText(string path); 52 | 53 | public abstract void CreateEmptyFile(string path); 54 | public abstract void CreateHardLink(string newLinkFilePath, string existingFilePath); 55 | public abstract void ChangeMode(string path, ushort mode); 56 | 57 | /// 58 | /// Write the specified contents to the specified file. By calling this method the caller is 59 | /// indicating that they expect the write to succeed. However, the caller is responsible for verifying that 60 | /// the write succeeded. 61 | /// 62 | /// Path to file 63 | /// File contents 64 | public abstract void WriteAllText(string path, string contents); 65 | public abstract IDisposable OpenFileAndWrite(string path, string data); 66 | 67 | /// 68 | /// Append the specified contents to the specified file. By calling this method the caller is 69 | /// indicating that they expect the write to succeed. However, the caller is responsible for verifying that 70 | /// the write succeeded. 71 | /// 72 | /// Path to file 73 | /// File contents 74 | public abstract void AppendAllText(string path, string contents); 75 | 76 | // Directory methods 77 | public abstract bool DirectoryExists(string path); 78 | public abstract void MoveDirectory(string sourcePath, string targetPath); 79 | public abstract void RenameDirectory(string workingDirectory, string source, string target); 80 | public abstract void CreateDirectory(string path); 81 | public abstract string EnumerateDirectory(string path); 82 | public abstract long FileSize(string path); 83 | 84 | /// 85 | /// A recursive delete of a directory 86 | /// 87 | public abstract string DeleteDirectory(string path); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/FileSystemRunners/ShellRunner.cs: -------------------------------------------------------------------------------- 1 | using Scalar.FunctionalTests.Tools; 2 | using System.Diagnostics; 3 | 4 | namespace Scalar.FunctionalTests.FileSystemRunners 5 | { 6 | public abstract class ShellRunner : FileSystemRunner 7 | { 8 | protected const string SuccessOutput = "True"; 9 | protected const string FailureOutput = "False"; 10 | 11 | protected abstract string FileName { get; } 12 | 13 | protected virtual string RunProcess(string arguments, string workingDirectory = "", string errorMsgDelimeter = "") 14 | { 15 | ProcessStartInfo startInfo = new ProcessStartInfo(); 16 | startInfo.UseShellExecute = false; 17 | startInfo.RedirectStandardOutput = true; 18 | startInfo.RedirectStandardError = true; 19 | startInfo.CreateNoWindow = true; 20 | startInfo.FileName = this.FileName; 21 | startInfo.Arguments = arguments; 22 | startInfo.WorkingDirectory = workingDirectory; 23 | 24 | ProcessResult result = ProcessHelper.Run(startInfo, errorMsgDelimeter: errorMsgDelimeter); 25 | return !string.IsNullOrEmpty(result.Output) ? result.Output : result.Errors; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/GlobalSetup.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.Tests; 3 | using Scalar.FunctionalTests.Tools; 4 | using System; 5 | using System.IO; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace Scalar.FunctionalTests 9 | { 10 | [SetUpFixture] 11 | public class GlobalSetup 12 | { 13 | [OneTimeSetUp] 14 | public void RunBeforeAnyTests() 15 | { 16 | } 17 | 18 | [OneTimeTearDown] 19 | public void RunAfterAllTests() 20 | { 21 | PrintTestCaseStats.PrintRunTimeStats(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/NUnitRunner.cs: -------------------------------------------------------------------------------- 1 | using NUnitLite; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace Scalar.Tests 8 | { 9 | public class NUnitRunner 10 | { 11 | private List args; 12 | 13 | public NUnitRunner(string[] args) 14 | { 15 | this.args = new List(args); 16 | } 17 | 18 | public string GetCustomArgWithParam(string arg) 19 | { 20 | string match = this.args.Where(a => a.StartsWith(arg + "=")).SingleOrDefault(); 21 | if (match == null) 22 | { 23 | return null; 24 | } 25 | 26 | this.args.Remove(match); 27 | return match.Substring(arg.Length + 1); 28 | } 29 | 30 | public bool HasCustomArg(string arg) 31 | { 32 | // We also remove it as we're checking, because nunit wouldn't understand what it means 33 | return this.args.Remove(arg); 34 | } 35 | 36 | public void AddGlobalSetupIfNeeded(string globalSetup) 37 | { 38 | // If there are any test filters, the GlobalSetup still needs to run so add it. 39 | if (this.args.Any(x => x.StartsWith("--test="))) 40 | { 41 | this.args.Add($"--test={globalSetup}"); 42 | } 43 | } 44 | 45 | public int RunTests(ICollection includeCategories, ICollection excludeCategories) 46 | { 47 | string filters = GetFiltersArgument(includeCategories, excludeCategories); 48 | if (filters.Length > 0) 49 | { 50 | this.args.Add("--where"); 51 | this.args.Add(filters); 52 | } 53 | 54 | DateTime now = DateTime.Now; 55 | int result = new AutoRun(Assembly.GetEntryAssembly()).Execute(this.args.ToArray()); 56 | 57 | Console.WriteLine("Completed test pass in {0}", DateTime.Now - now); 58 | Console.WriteLine(); 59 | 60 | return result; 61 | } 62 | 63 | private static string GetFiltersArgument(ICollection includeCategories, ICollection excludeCategories) 64 | { 65 | string filters = string.Empty; 66 | if (includeCategories != null && includeCategories.Any()) 67 | { 68 | filters = "(" + string.Join("||", includeCategories.Select(x => $"cat=={x}")) + ")"; 69 | } 70 | 71 | if (excludeCategories != null && excludeCategories.Any()) 72 | { 73 | filters += (filters.Length > 0 ? "&&" : string.Empty) + string.Join("&&", excludeCategories.Select(x => $"cat!={x}")); 74 | } 75 | 76 | return filters; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Program.cs: -------------------------------------------------------------------------------- 1 | using Scalar.FunctionalTests.Properties; 2 | using Scalar.FunctionalTests.Tools; 3 | using Scalar.Tests; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace Scalar.FunctionalTests 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | Properties.Settings.Default.Initialize(); 17 | NUnitRunner runner = new NUnitRunner(args); 18 | runner.AddGlobalSetupIfNeeded("Scalar.FunctionalTests.GlobalSetup"); 19 | 20 | if (runner.HasCustomArg("--no-shared-scalar-cache")) 21 | { 22 | Console.WriteLine("Running without a shared git object cache"); 23 | ScalarTestConfig.NoSharedCache = true; 24 | } 25 | 26 | if (runner.HasCustomArg("--test-git-on-path")) 27 | { 28 | Console.WriteLine("Running tests against Git on path"); 29 | ScalarTestConfig.TestGitOnPath = true; 30 | } 31 | 32 | if (runner.HasCustomArg("--test-scalar-on-path")) 33 | { 34 | Console.WriteLine("Running tests against Scalar on path"); 35 | ScalarTestConfig.TestScalarOnPath = true; 36 | } 37 | 38 | string trace2Output = runner.GetCustomArgWithParam("--trace2-output"); 39 | if (trace2Output != null) 40 | { 41 | Console.WriteLine($"Sending trace2 output to {trace2Output}"); 42 | Environment.SetEnvironmentVariable("GIT_TRACE2_EVENT", trace2Output); 43 | } 44 | 45 | ScalarTestConfig.LocalCacheRoot = runner.GetCustomArgWithParam("--shared-scalar-cache-root"); 46 | 47 | HashSet includeCategories = new HashSet(); 48 | HashSet excludeCategories = new HashSet(); 49 | 50 | // Run all GitRepoTests with sparse mode 51 | ScalarTestConfig.GitRepoTestsValidateWorkTree = 52 | new object[] 53 | { 54 | new object[] { Settings.ValidateWorkingTreeMode.SparseMode }, 55 | }; 56 | 57 | // Run maintenance tests in both `scalar run` and `git maintenance run` modes 58 | ScalarTestConfig.MaintenanceMode = 59 | new object[] 60 | { 61 | new object[] { Settings.MaintenanceMode.Scalar }, 62 | new object[] { Settings.MaintenanceMode.Git }, 63 | }; 64 | 65 | if (runner.HasCustomArg("--full-suite")) 66 | { 67 | Console.WriteLine("Running the full suite of tests"); 68 | 69 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 70 | { 71 | ScalarTestConfig.FileSystemRunners = FileSystemRunners.FileSystemRunner.AllWindowsRunners; 72 | } 73 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || 74 | RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 75 | { 76 | ScalarTestConfig.FileSystemRunners = FileSystemRunners.FileSystemRunner.AllPOSIXRunners; 77 | } 78 | } 79 | else 80 | { 81 | ScalarTestConfig.FileSystemRunners = FileSystemRunners.FileSystemRunner.DefaultRunners; 82 | } 83 | 84 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || 85 | RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 86 | { 87 | excludeCategories.Add(Categories.WindowsOnly); 88 | } 89 | else 90 | { 91 | excludeCategories.Add(Categories.POSIXOnly); 92 | } 93 | 94 | // For now, run all of the tests not flagged as needing to be updated to work 95 | // with the non-virtualized solution 96 | excludeCategories.Add(Categories.NeedsUpdatesForNonVirtualizedMode); 97 | 98 | ScalarTestConfig.RepoToClone = 99 | runner.GetCustomArgWithParam("--repo-to-clone") 100 | ?? Properties.Settings.Default.RepoToClone; 101 | 102 | RunBeforeAnyTests(); 103 | Environment.ExitCode = runner.RunTests(includeCategories, excludeCategories); 104 | 105 | if (Debugger.IsAttached) 106 | { 107 | Console.WriteLine("Tests completed. Press Enter to exit."); 108 | Console.ReadLine(); 109 | } 110 | } 111 | 112 | private static void RunBeforeAnyTests() 113 | { 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Scalar.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/ScalarTestConfig.cs: -------------------------------------------------------------------------------- 1 | using Scalar.FunctionalTests.Tools; 2 | using System; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Scalar.FunctionalTests 7 | { 8 | public static class ScalarTestConfig 9 | { 10 | public static string RepoToClone { get; set; } 11 | 12 | public static bool NoSharedCache { get; set; } 13 | 14 | public static string LocalCacheRoot { get; set; } 15 | 16 | public static string DefaultLocalCacheRoot { 17 | get 18 | { 19 | string homeDirectory = null; 20 | string cachePath = TestConstants.DefaultScalarCacheFolderName; 21 | 22 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 23 | { 24 | homeDirectory = Path.GetPathRoot(Properties.Settings.Default.EnlistmentRoot); 25 | } 26 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 27 | { 28 | homeDirectory = Environment.GetEnvironmentVariable( 29 | TestConstants.POSIXPlatform.EnvironmentVariables.LocalUserFolder); 30 | } 31 | else 32 | { 33 | // On Linux we use a local cache path per the XDG Base Directory Specification. 34 | homeDirectory = Environment.GetEnvironmentVariable( 35 | TestConstants.LinuxPlatform.EnvironmentVariables.LocalUserCacheFolder); 36 | if (!string.IsNullOrEmpty(homeDirectory)) 37 | { 38 | cachePath = TestConstants.LinuxPlatform.LocalScalarFolderName; 39 | } 40 | else 41 | { 42 | homeDirectory = Environment.GetEnvironmentVariable( 43 | TestConstants.POSIXPlatform.EnvironmentVariables.LocalUserFolder); 44 | cachePath = TestConstants.LinuxPlatform.LocalScalarCachePath; 45 | } 46 | 47 | } 48 | 49 | return Path.Combine(homeDirectory, cachePath); 50 | } 51 | } 52 | 53 | public static object[] FileSystemRunners { get; set; } 54 | 55 | public static object[] GitRepoTestsValidateWorkTree { get; set; } 56 | 57 | public static object[] MaintenanceMode { get; set; } 58 | 59 | public static bool TestGitOnPath { get; set; } 60 | 61 | public static string PathToGit 62 | { 63 | get 64 | { 65 | string gitBinaryFileName = "git" + Properties.Settings.Default.BinaryFileNameExtension; 66 | return 67 | TestGitOnPath ? 68 | gitBinaryFileName : 69 | Path.Combine(Properties.Settings.Default.PathToGitRoot, gitBinaryFileName); 70 | } 71 | } 72 | 73 | public static bool TestScalarOnPath { get; set; } 74 | 75 | public static string PathToScalar 76 | { 77 | get 78 | { 79 | return Properties.Settings.Default.PathToScalar; 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Scalar.FunctionalTests.Properties 6 | { 7 | public static class Settings 8 | { 9 | public enum ValidateWorkingTreeMode 10 | { 11 | None = 0, 12 | Full = 1, 13 | SparseMode = 2, 14 | } 15 | 16 | public enum MaintenanceMode 17 | { 18 | Scalar = 0, 19 | Git = 1, 20 | } 21 | 22 | public static class Default 23 | { 24 | public static string CurrentDirectory { get; private set; } 25 | 26 | public static string RepoToClone { get; set; } 27 | public static string PathToBash { get; set; } 28 | public static string PathToScalar { get; set; } 29 | public static string Commitish { get; set; } 30 | public static string CommitId { get; set; } 31 | public static string ControlGitRepoRoot { get; set; } 32 | public static string EnlistmentRoot { get; set; } 33 | public static string PathToGitRoot { get; set; } 34 | public static string BinaryFileNameExtension { get; set; } 35 | 36 | public static void Initialize() 37 | { 38 | CurrentDirectory = Path.GetFullPath(Path.GetDirectoryName(Environment.GetCommandLineArgs()[0])); 39 | 40 | RepoToClone = @"https://gvfs.visualstudio.com/ci/_git/ForTests"; 41 | Commitish = @"FunctionalTests/20180214"; 42 | CommitId = "2797fbb8358bb2e0c12d6f3b42a60b43f7655edf"; 43 | 44 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 45 | { 46 | EnlistmentRoot = @"C:\Repos\ScalarFunctionalTests\enlistment"; 47 | PathToScalar = @"Scalar.exe"; 48 | PathToGitRoot = @"C:\Program Files\Git\cmd"; 49 | PathToBash = @"C:\Program Files\Git\bin\bash.exe"; 50 | 51 | ControlGitRepoRoot = @"C:\Repos\ScalarFunctionalTests\ControlRepo"; 52 | BinaryFileNameExtension = ".exe"; 53 | } 54 | else 55 | { 56 | string root = Path.Combine( 57 | Environment.GetEnvironmentVariable("HOME"), 58 | "Scalar.FT"); 59 | EnlistmentRoot = Path.Combine(root, "test"); 60 | ControlGitRepoRoot = Path.Combine(root, "control"); 61 | PathToScalar = "scalar"; 62 | PathToGitRoot = "/usr/local/bin"; 63 | PathToBash = "/bin/bash"; 64 | BinaryFileNameExtension = string.Empty; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/EnlistmentPerFixture/CloneTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.FileSystemRunners; 3 | using Scalar.FunctionalTests.Should; 4 | using Scalar.FunctionalTests.Tools; 5 | using Scalar.Tests.Should; 6 | using System; 7 | using System.Diagnostics; 8 | using System.IO; 9 | 10 | namespace Scalar.FunctionalTests.Tests.EnlistmentPerFixture 11 | { 12 | [TestFixture] 13 | public class CloneTests : TestsWithEnlistmentPerFixture 14 | { 15 | private const int ScalarGenericError = 128; 16 | 17 | [TestCase] 18 | public void CloneWithLocalCachePathWithinSrc() 19 | { 20 | string newEnlistmentRoot = ScalarFunctionalTestEnlistment.GetUniqueEnlistmentRoot(); 21 | string localCachePath = Path.Combine(newEnlistmentRoot, "src", ".scalarCache"); 22 | ProcessResult result = this.RunCloneCommand( 23 | Path.GetDirectoryName(this.Enlistment.EnlistmentRoot), 24 | newEnlistmentRoot, 25 | $"--local-cache-path {localCachePath}"); 26 | result.ExitCode.ShouldEqual(ScalarGenericError); 27 | 28 | localCachePath = Path.Combine(newEnlistmentRoot, "SRC", ".scalarCache"); 29 | 30 | result = this.RunCloneCommand( 31 | Path.GetDirectoryName(this.Enlistment.EnlistmentRoot), 32 | newEnlistmentRoot, 33 | $"--local-cache-path {localCachePath}"); 34 | if (FileSystemHelpers.CaseSensitiveFileSystem) 35 | { 36 | result.ExitCode.ShouldEqual(0, result.Errors); 37 | } 38 | else 39 | { 40 | result.ExitCode.ShouldEqual(ScalarGenericError); 41 | } 42 | 43 | RepositoryHelpers.DeleteTestDirectory(newEnlistmentRoot); 44 | } 45 | 46 | [TestCase] 47 | public void SparseCloneWithNoFetchOfCommitsAndTreesSucceeds() 48 | { 49 | ScalarFunctionalTestEnlistment enlistment = null; 50 | 51 | try 52 | { 53 | enlistment = ScalarFunctionalTestEnlistment.CloneWithPerRepoCache(ScalarTestConfig.PathToScalar, skipFetchCommitsAndTrees: true); 54 | 55 | ProcessResult result = GitProcess.InvokeProcess(enlistment.RepoRoot, "status"); 56 | result.ExitCode.ShouldEqual(0, result.Errors); 57 | } 58 | finally 59 | { 60 | enlistment?.DeleteAll(); 61 | } 62 | } 63 | 64 | [TestCase] 65 | [Category(Categories.POSIXOnly)] 66 | public void CloneWithDefaultLocalCacheLocation() 67 | { 68 | FileSystemRunner fileSystem = FileSystemRunner.DefaultRunner; 69 | string defaultLocalCacheRoot = ScalarTestConfig.DefaultLocalCacheRoot; 70 | fileSystem.CreateDirectory(defaultLocalCacheRoot); 71 | defaultLocalCacheRoot.ShouldBeADirectory(fileSystem); 72 | 73 | string newEnlistmentRoot = ScalarFunctionalTestEnlistment.GetUniqueEnlistmentRoot(); 74 | 75 | ProcessResult result = this.RunCloneCommand( 76 | Properties.Settings.Default.EnlistmentRoot, 77 | newEnlistmentRoot, 78 | "--no-fetch-commits-and-trees"); 79 | result.ExitCode.ShouldEqual(0, result.Errors); 80 | 81 | string gitObjectsRoot = ScalarHelpers.GetObjectsRootFromGitConfig(Path.Combine(newEnlistmentRoot, "src")); 82 | 83 | gitObjectsRoot.StartsWith(defaultLocalCacheRoot, FileSystemHelpers.PathComparison).ShouldBeTrue($"Git objects root did not default to using {defaultLocalCacheRoot}"); 84 | 85 | RepositoryHelpers.DeleteTestDirectory(newEnlistmentRoot); 86 | } 87 | 88 | [TestCase] 89 | public void CloneToPathWithSpaces() 90 | { 91 | ScalarFunctionalTestEnlistment enlistment = ScalarFunctionalTestEnlistment.CloneEnlistmentWithSpacesInPath(ScalarTestConfig.PathToScalar); 92 | enlistment.DeleteAll(); 93 | } 94 | 95 | [TestCase] 96 | public void CloneCreatesCorrectFilesInRoot() 97 | { 98 | ScalarFunctionalTestEnlistment enlistment = ScalarFunctionalTestEnlistment.Clone(ScalarTestConfig.PathToScalar); 99 | try 100 | { 101 | Directory.GetFiles(enlistment.EnlistmentRoot).ShouldBeEmpty("There should be no files in the enlistment root after cloning"); 102 | string[] directories = Directory.GetDirectories(enlistment.EnlistmentRoot); 103 | directories.Length.ShouldEqual(1); 104 | directories.ShouldContain(x => Path.GetFileName(x).Equals("src", FileSystemHelpers.PathComparison)); 105 | } 106 | finally 107 | { 108 | enlistment.DeleteAll(); 109 | } 110 | } 111 | 112 | private ProcessResult RunCloneCommand(string workingDirectoryPath, string enlistmentRootPath, string extraArgs = null) 113 | { 114 | ProcessStartInfo processInfo = new ProcessStartInfo(ScalarTestConfig.PathToScalar); 115 | processInfo.Arguments = $"clone {ScalarTestConfig.RepoToClone} {enlistmentRootPath} {extraArgs}"; 116 | processInfo.WorkingDirectory = workingDirectoryPath; 117 | processInfo.WindowStyle = ProcessWindowStyle.Hidden; 118 | processInfo.CreateNoWindow = true; 119 | processInfo.UseShellExecute = false; 120 | processInfo.RedirectStandardOutput = true; 121 | 122 | return ProcessHelper.Run(processInfo); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/EnlistmentPerFixture/DiagnoseTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.Tests.Should; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace Scalar.FunctionalTests.Tests.EnlistmentPerFixture 9 | { 10 | [TestFixture] 11 | [NonParallelizable] 12 | public class DiagnoseTests : TestsWithEnlistmentPerFixture 13 | { 14 | [TestCase] 15 | public void DiagnoseProducesZipFile() 16 | { 17 | Directory.Exists(this.Enlistment.DiagnosticsRoot).ShouldEqual(false); 18 | string output = this.Enlistment.Diagnose(); 19 | output.ShouldNotContain(ignoreCase: true, unexpectedSubstrings: "Failed"); 20 | 21 | IEnumerable files = Directory.EnumerateFiles(this.Enlistment.DiagnosticsRoot); 22 | files.ShouldBeNonEmpty(); 23 | string zipFilePath = files.First(); 24 | 25 | zipFilePath.EndsWith(".zip").ShouldEqual(true); 26 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && 27 | !output.Contains(zipFilePath)) 28 | zipFilePath = zipFilePath.Replace('\\', '/'); 29 | output.ShouldContain(zipFilePath); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/EnlistmentPerFixture/FetchStepTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.FileSystemRunners; 3 | using Scalar.FunctionalTests.Properties; 4 | using Scalar.FunctionalTests.Should; 5 | using Scalar.FunctionalTests.Tools; 6 | using Scalar.Tests.Should; 7 | using System.IO; 8 | using System.Linq; 9 | 10 | namespace Scalar.FunctionalTests.Tests.EnlistmentPerFixture 11 | { 12 | [TestFixtureSource(typeof(TestsWithEnlistmentPerFixture), nameof(TestsWithEnlistmentPerFixture.MaintenanceMode))] 13 | [Category(Categories.Maintenance)] 14 | [NonParallelizable] 15 | public class FetchStepTests : TestsWithEnlistmentPerFixture 16 | { 17 | private const string FetchCommitsAndTreesLock = "fetch-commits-trees.lock"; 18 | 19 | private FileSystemRunner fileSystem; 20 | private Settings.MaintenanceMode maintenanceMode; 21 | 22 | public FetchStepTests(Settings.MaintenanceMode maintenanceMode) 23 | { 24 | this.fileSystem = new SystemIORunner(); 25 | this.maintenanceMode = maintenanceMode; 26 | } 27 | 28 | [TestCase] 29 | public void FetchStepReleasesFetchLockFile() 30 | { 31 | this.RunFetchTask(); 32 | string fetchCommitsLockFile = Path.Combine( 33 | ScalarHelpers.GetObjectsRootFromGitConfig(this.Enlistment.RepoRoot), 34 | "pack", 35 | FetchCommitsAndTreesLock); 36 | this.fileSystem.WriteAllText(fetchCommitsLockFile, this.Enlistment.EnlistmentRoot); 37 | fetchCommitsLockFile.ShouldBeAFile(this.fileSystem); 38 | 39 | this.fileSystem 40 | .EnumerateDirectory(this.Enlistment.GetPackRoot(this.fileSystem)) 41 | .Split() 42 | .Where(file => string.Equals(Path.GetExtension(file), ".keep", FileSystemHelpers.PathComparison)) 43 | .Count() 44 | .ShouldEqual(1, "Incorrect number of .keep files in pack directory"); 45 | 46 | this.RunFetchTask(); 47 | 48 | // Using FileShare.None ensures we test on both Windows, where WindowsFileBasedLock uses 49 | // FileShare.Read to open the lock file, and on Mac/Linux, where the .NET Core libraries 50 | // implement FileShare.None using flock(2) with LOCK_EX and thus will collide with our 51 | // Mac/Linux FileBasedLock implementations which do the same, should the FetchStep 52 | // have failed to release its lock. 53 | FileStream stream = new FileStream(fetchCommitsLockFile, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None); 54 | stream.Dispose(); 55 | } 56 | 57 | private void RunFetchTask() 58 | { 59 | if (this.maintenanceMode == Settings.MaintenanceMode.Scalar) 60 | { 61 | this.Enlistment.RunVerb("fetch"); 62 | } 63 | else if (this.maintenanceMode == Settings.MaintenanceMode.Git) 64 | { 65 | this.Enlistment.RunMaintenanceTask("prefetch"); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/EnlistmentPerFixture/FetchStepWithoutSharedCacheTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.FileSystemRunners; 3 | using Scalar.FunctionalTests.Should; 4 | using Scalar.FunctionalTests.Tools; 5 | using Scalar.Tests.Should; 6 | using System; 7 | using System.IO; 8 | 9 | namespace Scalar.FunctionalTests.Tests.EnlistmentPerFixture 10 | { 11 | [TestFixture] 12 | public class FetchStepWithoutSharedCacheTests : TestsWithEnlistmentPerFixture 13 | { 14 | private const string PrefetchPackPrefix = "prefetch"; 15 | private const string TempPackFolder = "tempPacks"; 16 | 17 | private FileSystemRunner fileSystem; 18 | 19 | // Set forcePerRepoObjectCache to true to avoid any of the tests inadvertently corrupting 20 | // the cache 21 | public FetchStepWithoutSharedCacheTests() 22 | : base(forcePerRepoObjectCache: true, skipFetchCommitsAndTreesDuringClone: true) 23 | { 24 | this.fileSystem = new SystemIORunner(); 25 | } 26 | 27 | private string PackRoot 28 | { 29 | get 30 | { 31 | return this.Enlistment.GetPackRoot(this.fileSystem); 32 | } 33 | } 34 | 35 | private string TempPackRoot 36 | { 37 | get 38 | { 39 | return Path.Combine(this.PackRoot, TempPackFolder); 40 | } 41 | } 42 | 43 | [TestCase, Order(1)] 44 | public void FetchStepCommitsToEmptyCache() 45 | { 46 | this.Enlistment.RunVerb("fetch"); 47 | 48 | // Verify prefetch pack(s) are in packs folder and have matching idx file 49 | string[] prefetchPacks = this.ReadPrefetchPackFileNames(); 50 | this.AllPrefetchPacksShouldHaveIdx(prefetchPacks); 51 | 52 | // Verify tempPacks is empty 53 | this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems(); 54 | } 55 | 56 | private void PackShouldHaveIdxFile(string pathPath) 57 | { 58 | string idxPath = Path.ChangeExtension(pathPath, ".idx"); 59 | idxPath.ShouldBeAFile(this.fileSystem).WithContents().Length.ShouldBeAtLeast(1, $"{idxPath} is unexepectedly empty"); 60 | } 61 | 62 | private void AllPrefetchPacksShouldHaveIdx(string[] prefetchPacks) 63 | { 64 | prefetchPacks.Length.ShouldBeAtLeast(1, "There should be at least one prefetch pack"); 65 | 66 | foreach (string prefetchPack in prefetchPacks) 67 | { 68 | this.PackShouldHaveIdxFile(prefetchPack); 69 | } 70 | } 71 | 72 | private string[] ReadPrefetchPackFileNames() 73 | { 74 | return Directory.GetFiles(this.PackRoot, $"{PrefetchPackPrefix}*.pack"); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/EnlistmentPerFixture/GitFetchTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.FileSystemRunners; 3 | using Scalar.FunctionalTests.Should; 4 | using Scalar.FunctionalTests.Tools; 5 | using Scalar.Tests.Should; 6 | using System.IO; 7 | using System.Linq; 8 | 9 | namespace Scalar.FunctionalTests.Tests.EnlistmentPerFixture 10 | { 11 | [TestFixture] 12 | public class GitFetchTests : TestsWithEnlistmentPerFixture 13 | { 14 | private const string PrefetchPackPrefix = "prefetch"; 15 | private const string TempPackFolder = "tempPacks"; 16 | 17 | private FileSystemRunner fileSystem; 18 | 19 | public GitFetchTests() 20 | : base(forcePerRepoObjectCache: true, skipFetchCommitsAndTreesDuringClone: true) 21 | { 22 | this.fileSystem = new SystemIORunner(); 23 | } 24 | 25 | private string PackRoot 26 | { 27 | get 28 | { 29 | return this.Enlistment.GetPackRoot(this.fileSystem); 30 | } 31 | } 32 | 33 | private string TempPackRoot 34 | { 35 | get 36 | { 37 | return Path.Combine(this.PackRoot, TempPackFolder); 38 | } 39 | } 40 | 41 | [TestCase, Order(1)] 42 | public void GitFetchDownloadsPrefetchPacks() 43 | { 44 | this.fileSystem.DeleteDirectory(this.PackRoot); 45 | 46 | GitHelpers.InvokeGitAgainstScalarRepo(this.Enlistment.RepoRoot, "fetch origin"); 47 | 48 | // Verify pack root has a prefetch pack 49 | this.PackRoot 50 | .ShouldBeADirectory(this.fileSystem) 51 | .WithItems() 52 | .Where(info => info.Name.StartsWith(PrefetchPackPrefix)) 53 | .ShouldBeNonEmpty(); 54 | 55 | // Verify tempPacks is empty 56 | this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/EnlistmentPerFixture/StatusVerbTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.FileSystemRunners; 3 | using Scalar.Tests.Should; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | namespace Scalar.FunctionalTests.Tests.EnlistmentPerFixture 8 | { 9 | [TestFixture] 10 | [Category(Categories.NeedsUpdatesForNonVirtualizedMode)] 11 | public class StatusVerbTests : TestsWithEnlistmentPerFixture 12 | { 13 | [TestCase] 14 | public void GitTrace() 15 | { 16 | Dictionary environmentVariables = new Dictionary(); 17 | 18 | this.Enlistment.Status(trace: "1"); 19 | this.Enlistment.Status(trace: "2"); 20 | 21 | string logPath = Path.Combine(this.Enlistment.RepoRoot, "log-file.txt"); 22 | this.Enlistment.Status(trace: logPath); 23 | 24 | FileSystemRunner fileSystem = new SystemIORunner(); 25 | fileSystem.FileExists(logPath).ShouldBeTrue(); 26 | string.IsNullOrWhiteSpace(fileSystem.ReadAllText(logPath)).ShouldBeFalse(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/EnlistmentPerFixture/TestsWithEnlistmentPerFixture.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.Tools; 3 | 4 | namespace Scalar.FunctionalTests.Tests.EnlistmentPerFixture 5 | { 6 | [TestFixture] 7 | public abstract class TestsWithEnlistmentPerFixture 8 | { 9 | private readonly bool forcePerRepoObjectCache; 10 | private readonly bool skipFetchCommitsAndTreesDuringClone; 11 | private readonly bool fullClone; 12 | 13 | public TestsWithEnlistmentPerFixture(bool forcePerRepoObjectCache = false, bool skipFetchCommitsAndTreesDuringClone = false, bool fullClone = true) 14 | { 15 | this.forcePerRepoObjectCache = forcePerRepoObjectCache; 16 | this.skipFetchCommitsAndTreesDuringClone = skipFetchCommitsAndTreesDuringClone; 17 | this.fullClone = fullClone; 18 | } 19 | 20 | public static object[] MaintenanceMode 21 | { 22 | get 23 | { 24 | return ScalarTestConfig.MaintenanceMode; 25 | } 26 | } 27 | 28 | public ScalarFunctionalTestEnlistment Enlistment 29 | { 30 | get; private set; 31 | } 32 | 33 | [OneTimeSetUp] 34 | public virtual void CreateEnlistment() 35 | { 36 | if (this.forcePerRepoObjectCache) 37 | { 38 | this.Enlistment = ScalarFunctionalTestEnlistment.CloneWithPerRepoCache( 39 | ScalarTestConfig.PathToScalar, 40 | this.skipFetchCommitsAndTreesDuringClone); 41 | } 42 | else 43 | { 44 | this.Enlistment = ScalarFunctionalTestEnlistment.Clone(ScalarTestConfig.PathToScalar, fullClone: this.fullClone); 45 | } 46 | } 47 | 48 | [OneTimeTearDown] 49 | public virtual void DeleteEnlistment() 50 | { 51 | if (this.Enlistment != null) 52 | { 53 | this.Enlistment.DeleteAll(); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/EnlistmentPerTestCase/TestsWithEnlistmentPerTestCase.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.Tools; 3 | 4 | namespace Scalar.FunctionalTests.Tests.EnlistmentPerTestCase 5 | { 6 | [TestFixture] 7 | public abstract class TestsWithEnlistmentPerTestCase 8 | { 9 | private readonly bool forcePerRepoObjectCache; 10 | 11 | public TestsWithEnlistmentPerTestCase(bool forcePerRepoObjectCache = false) 12 | { 13 | this.forcePerRepoObjectCache = forcePerRepoObjectCache; 14 | } 15 | 16 | public ScalarFunctionalTestEnlistment Enlistment 17 | { 18 | get; private set; 19 | } 20 | 21 | [SetUp] 22 | public virtual void CreateEnlistment() 23 | { 24 | if (this.forcePerRepoObjectCache) 25 | { 26 | this.Enlistment = ScalarFunctionalTestEnlistment.CloneWithPerRepoCache( 27 | ScalarTestConfig.PathToScalar, 28 | skipFetchCommitsAndTrees: false); 29 | } 30 | else 31 | { 32 | this.Enlistment = ScalarFunctionalTestEnlistment.Clone(ScalarTestConfig.PathToScalar); 33 | } 34 | } 35 | 36 | [TearDown] 37 | public virtual void DeleteEnlistment() 38 | { 39 | if (this.Enlistment != null) 40 | { 41 | this.Enlistment.DeleteAll(); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/GitRepoPerFixture/TestsWithGitRepoPerFixture.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.Tools; 3 | 4 | namespace Scalar.FunctionalTests.Tests.GitRepoPerFixture 5 | { 6 | [TestFixture] 7 | public class TestsWithGitRepoPerFixture 8 | { 9 | public ScalarFunctionalTestEnlistment Enlistment 10 | { 11 | get; private set; 12 | } 13 | 14 | [OneTimeSetUp] 15 | public virtual void CreateRepo() 16 | { 17 | this.Enlistment = ScalarFunctionalTestEnlistment.CloneGitRepo(ScalarTestConfig.PathToScalar); 18 | } 19 | 20 | [OneTimeTearDown] 21 | public virtual void DeleteRepo() 22 | { 23 | if (this.Enlistment != null) 24 | { 25 | this.Enlistment.DeleteAll(); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/MultiEnlistmentTests/RepoRegistryTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.Tools; 3 | using Scalar.Tests.Should; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace Scalar.FunctionalTests.Tests.MultiEnlistmentTests 8 | { 9 | [TestFixture] 10 | public class RepoRegistryTests : TestsWithMultiEnlistment 11 | { 12 | [TestCase] 13 | public void ServiceListRegistered() 14 | { 15 | ScalarFunctionalTestEnlistment enlistment1 = this.CreateNewEnlistment(); 16 | ScalarFunctionalTestEnlistment enlistment2 = this.CreateNewEnlistment(); 17 | 18 | string[] repoRootList = new string[] { enlistment1.EnlistmentRoot, enlistment2.EnlistmentRoot }; 19 | 20 | // Do not check for unexpected repos, as other repos on the machine may be registered while 21 | // this test is running 22 | this.RunListCommand(enlistment1.EnlistmentRoot, expectedRepoRoots: repoRootList); 23 | } 24 | 25 | [TestCase] 26 | public void UnregisterRemovesFromList() 27 | { 28 | ScalarFunctionalTestEnlistment enlistment1 = this.CreateNewEnlistment(); 29 | ScalarFunctionalTestEnlistment enlistment2 = this.CreateNewEnlistment(); 30 | 31 | string[] repoRootList = new string[] { enlistment1.EnlistmentRoot, enlistment2.EnlistmentRoot }; 32 | 33 | string workDir = Directory.GetCurrentDirectory(); 34 | 35 | this.RunListCommand(workDir, expectedRepoRoots: repoRootList); 36 | 37 | ScalarProcess process = new ScalarProcess(ScalarTestConfig.PathToScalar, "", ""); 38 | process.Unregister(enlistment1.EnlistmentRoot); 39 | 40 | this.RunListCommand( 41 | workDir, 42 | expectedRepoRoots: new[] { enlistment2.EnlistmentRoot }, 43 | unexpectedRepoRoots: new[] { enlistment1.EnlistmentRoot }); 44 | } 45 | 46 | private void RunListCommand(string workdir, string[] expectedRepoRoots, string[] unexpectedRepoRoots = null) 47 | { 48 | ScalarProcess scalarProcess = new ScalarProcess( 49 | ScalarTestConfig.PathToScalar, 50 | enlistmentRoot: workdir, 51 | localCacheRoot: null); 52 | 53 | string result = scalarProcess.ListRepos(); 54 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && 55 | !result.Contains('\\') && 56 | expectedRepoRoots.Length > 0 && 57 | !expectedRepoRoots[0].Contains('/')) { 58 | result = result.Replace('/', '\\'); 59 | } 60 | result.ShouldContain(expectedRepoRoots); 61 | 62 | if (unexpectedRepoRoots != null) 63 | { 64 | result.ShouldNotContain(ignoreCase: false, unexpectedRepoRoots); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/MultiEnlistmentTests/ScalarCloneFromGithub.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.FileSystemRunners; 3 | using Scalar.FunctionalTests.Tools; 4 | using Scalar.Tests.Should; 5 | using System; 6 | using System.IO; 7 | using System.Linq; 8 | 9 | namespace Scalar.FunctionalTests.Tests.MultiEnlistmentTests 10 | { 11 | [Category(Categories.GitRepository)] 12 | public class ScalarCloneFromGithub : TestsWithMultiEnlistment 13 | { 14 | private static readonly string MicrosoftScalarHttp = "https://github.com/microsoft/scalar"; 15 | 16 | private FileSystemRunner fileSystem; 17 | 18 | public ScalarCloneFromGithub() 19 | { 20 | this.fileSystem = new SystemIORunner(); 21 | } 22 | 23 | [TestCase] 24 | public void PartialCloneHttps() 25 | { 26 | ScalarFunctionalTestEnlistment enlistment = this.CreateNewEnlistment( 27 | url: MicrosoftScalarHttp, 28 | branch: "for-tests", 29 | fullClone: false); 30 | 31 | VerifyPartialCloneBehavior(enlistment); 32 | } 33 | 34 | private void VerifyPartialCloneBehavior(ScalarFunctionalTestEnlistment enlistment) 35 | { 36 | this.fileSystem.DirectoryExists(enlistment.RepoRoot).ShouldBeTrue($"'{enlistment.RepoRoot}' does not exist"); 37 | 38 | string gitPack = Path.Combine(enlistment.RepoRoot, ".git", "objects", "pack"); 39 | this.fileSystem.DirectoryExists(gitPack).ShouldBeTrue($"'{gitPack}' does not exist"); 40 | 41 | void checkPacks(string dir, int count, string when) 42 | { 43 | string dirContents = this.fileSystem 44 | .EnumerateDirectory(dir); 45 | 46 | dirContents 47 | .Split() 48 | .Where(file => string.Equals(Path.GetExtension(file), ".pack", FileSystemHelpers.PathComparison)) 49 | .Count() 50 | .ShouldEqual(count, $"'{dir}' after '{when}': '{dirContents}'"); 51 | } 52 | 53 | // Two packs for clone: commits and trees, blobs at root 54 | checkPacks(gitPack, 2, "clone"); 55 | 56 | string srcScalar = Path.Combine(enlistment.RepoRoot, "Scalar"); 57 | this.fileSystem.DirectoryExists(srcScalar).ShouldBeFalse($"'{srcScalar}' should not exist due to sparse-checkout"); 58 | 59 | ProcessResult sparseCheckoutResult = GitProcess.InvokeProcess(enlistment.RepoRoot, "sparse-checkout disable"); 60 | sparseCheckoutResult.ExitCode.ShouldEqual(0, "git sparse-checkout disable exit code"); 61 | 62 | this.fileSystem.DirectoryExists(srcScalar).ShouldBeTrue($"'{srcScalar}' should exist after sparse-checkout"); 63 | 64 | // Three packs for sparse-chekcout: commits and trees, blobs at root, blobs outside root 65 | checkPacks(gitPack, 3, "sparse-checkout"); 66 | 67 | ProcessResult checkoutResult = GitProcess.InvokeProcess(enlistment.RepoRoot, "checkout HEAD~10"); 68 | checkoutResult.ExitCode.ShouldEqual(0, "git checkout exit code"); 69 | 70 | // Four packs for chekcout: commits and trees, blobs at root, blobs outside root, checkout diff 71 | checkPacks(gitPack, 4, "checkout"); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/MultiEnlistmentTests/TestsWithMultiEnlistment.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Scalar.FunctionalTests.Tools; 3 | using System.Collections.Generic; 4 | 5 | namespace Scalar.FunctionalTests.Tests.MultiEnlistmentTests 6 | { 7 | public class TestsWithMultiEnlistment 8 | { 9 | private List enlistmentsToDelete = new List(); 10 | 11 | [TearDown] 12 | public void DeleteEnlistments() 13 | { 14 | foreach (ScalarFunctionalTestEnlistment enlistment in this.enlistmentsToDelete) 15 | { 16 | enlistment.DeleteAll(); 17 | } 18 | 19 | this.OnTearDownEnlistmentsDeleted(); 20 | 21 | this.enlistmentsToDelete.Clear(); 22 | } 23 | 24 | /// 25 | /// Can be overridden for custom [TearDown] steps that occur after the test enlistments have been deleted 26 | /// 27 | protected virtual void OnTearDownEnlistmentsDeleted() 28 | { 29 | } 30 | 31 | protected ScalarFunctionalTestEnlistment CreateNewEnlistment( 32 | string localCacheRoot = null, 33 | string branch = null, 34 | bool skipFetchCommitsAndTrees = false, 35 | string url = null, 36 | bool fullClone = true) 37 | { 38 | ScalarFunctionalTestEnlistment output = ScalarFunctionalTestEnlistment.Clone( 39 | ScalarTestConfig.PathToScalar, 40 | branch, 41 | localCacheRoot, 42 | skipFetchCommitsAndTrees, 43 | fullClone: fullClone, 44 | url: url); 45 | this.enlistmentsToDelete.Add(output); 46 | return output; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/PrintTestCaseStats.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using NUnit.Framework.Interfaces; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | [assembly: Scalar.FunctionalTests.Tests.PrintTestCaseStats] 9 | 10 | namespace Scalar.FunctionalTests.Tests 11 | { 12 | public class PrintTestCaseStats : TestActionAttribute 13 | { 14 | private const string StartTimeKey = "StartTime"; 15 | 16 | private static ConcurrentDictionary fixtureRunTimes = new ConcurrentDictionary(); 17 | private static ConcurrentDictionary testRunTimes = new ConcurrentDictionary(); 18 | 19 | public override ActionTargets Targets 20 | { 21 | get { return ActionTargets.Test; } 22 | } 23 | 24 | public static void PrintRunTimeStats() 25 | { 26 | Console.WriteLine(); 27 | Console.WriteLine("Fixture run times:"); 28 | foreach (KeyValuePair fixture in fixtureRunTimes.OrderByDescending(kvp => kvp.Value)) 29 | { 30 | Console.WriteLine(" {0}\t{1}", fixture.Value, fixture.Key); 31 | } 32 | 33 | Console.WriteLine(); 34 | Console.WriteLine("Test case run times:"); 35 | foreach (KeyValuePair testcase in testRunTimes.OrderByDescending(kvp => kvp.Value)) 36 | { 37 | Console.WriteLine(" {0}\t{1}", testcase.Value, testcase.Key); 38 | } 39 | } 40 | 41 | public override void BeforeTest(ITest test) 42 | { 43 | test.Properties.Add(StartTimeKey, DateTime.Now); 44 | } 45 | 46 | public override void AfterTest(ITest test) 47 | { 48 | DateTime startTime = (DateTime)test.Properties.Get(StartTimeKey); 49 | DateTime endTime = DateTime.Now; 50 | TimeSpan duration = endTime - startTime; 51 | string message = TestContext.CurrentContext.Result.Message; 52 | TestStatus status = TestContext.CurrentContext.Result.Outcome.Status; 53 | 54 | Console.WriteLine("Test " + test.FullName); 55 | Console.WriteLine($"{status} at {endTime.ToLongTimeString()} taking {duration}"); 56 | if (status != TestStatus.Passed) 57 | { 58 | Console.WriteLine(message); 59 | } 60 | 61 | Console.WriteLine(); 62 | 63 | fixtureRunTimes.AddOrUpdate( 64 | test.ClassName, 65 | duration, 66 | (key, existingValue) => existingValue + duration); 67 | testRunTimes.TryAdd(test.FullName, duration); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tests/TestResultsHelper.cs: -------------------------------------------------------------------------------- 1 | using Scalar.FunctionalTests.Tools; 2 | using Scalar.Tests.Should; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | namespace Scalar.FunctionalTests.Tests 9 | { 10 | public static class TestResultsHelper 11 | { 12 | public static void OutputScalarLogs(ScalarFunctionalTestEnlistment enlistment) 13 | { 14 | if (enlistment == null) 15 | { 16 | return; 17 | } 18 | 19 | Console.WriteLine("Scalar logs output attached below.\n\n"); 20 | 21 | foreach (string filename in GetAllFilesInDirectory(enlistment.ScalarLogsRoot)) 22 | { 23 | OutputFileContents(filename); 24 | } 25 | } 26 | 27 | public static void OutputFileContents(string filename, Action contentsValidator = null) 28 | { 29 | try 30 | { 31 | using (StreamReader reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) 32 | { 33 | Console.WriteLine("----- {0} -----", filename); 34 | 35 | string contents = reader.ReadToEnd(); 36 | 37 | if (contentsValidator != null) 38 | { 39 | contentsValidator(contents); 40 | } 41 | 42 | Console.WriteLine(contents + "\n\n"); 43 | } 44 | } 45 | catch (IOException ex) 46 | { 47 | Console.WriteLine("Unable to read logfile at {0}: {1}", filename, ex.ToString()); 48 | } 49 | } 50 | 51 | public static IEnumerable GetAllFilesInDirectory(string folderName) 52 | { 53 | DirectoryInfo directory = new DirectoryInfo(folderName); 54 | if (!directory.Exists) 55 | { 56 | return Enumerable.Empty(); 57 | } 58 | 59 | return directory.GetFiles().Select(file => file.FullName); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/ControlGitRepo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Scalar.FunctionalTests.Tools 5 | { 6 | public class ControlGitRepo 7 | { 8 | static ControlGitRepo() 9 | { 10 | if (!Directory.Exists(CachePath)) 11 | { 12 | GitProcess.Invoke(Environment.SystemDirectory, "clone " + ScalarTestConfig.RepoToClone + " " + CachePath + " --bare"); 13 | } 14 | else 15 | { 16 | GitProcess.Invoke(CachePath, "fetch origin +refs/*:refs/*"); 17 | } 18 | } 19 | 20 | private ControlGitRepo(string repoUrl, string rootPath, string commitish) 21 | { 22 | this.RootPath = rootPath; 23 | this.RepoUrl = repoUrl; 24 | this.Commitish = commitish; 25 | } 26 | 27 | public string RootPath { get; private set; } 28 | public string RepoUrl { get; private set; } 29 | public string Commitish { get; private set; } 30 | 31 | private static string CachePath 32 | { 33 | get { return Path.Combine(Properties.Settings.Default.ControlGitRepoRoot, "cache"); } 34 | } 35 | 36 | public static ControlGitRepo Create(string commitish = null) 37 | { 38 | string clonePath = Path.Combine(Properties.Settings.Default.ControlGitRepoRoot, Guid.NewGuid().ToString("N")); 39 | return new ControlGitRepo( 40 | ScalarTestConfig.RepoToClone, 41 | clonePath, 42 | commitish == null ? Properties.Settings.Default.Commitish : commitish); 43 | } 44 | 45 | // 46 | // IMPORTANT! These must parallel the settings in ScalarVerb:TrySetRequiredGitConfigSettings 47 | // 48 | public void Initialize() 49 | { 50 | Directory.CreateDirectory(this.RootPath); 51 | GitProcess.Invoke(this.RootPath, "init"); 52 | GitProcess.Invoke(this.RootPath, "config core.autocrlf false"); 53 | GitProcess.Invoke(this.RootPath, "config core.editor true"); 54 | GitProcess.Invoke(this.RootPath, "config merge.stat false"); 55 | GitProcess.Invoke(this.RootPath, "config merge.renames false"); 56 | GitProcess.Invoke(this.RootPath, "config advice.statusUoption false"); 57 | GitProcess.Invoke(this.RootPath, "config advice.sparseIndexExpanded false"); 58 | GitProcess.Invoke(this.RootPath, "config core.abbrev 40"); 59 | GitProcess.Invoke(this.RootPath, "config pack.useSparse true"); 60 | GitProcess.Invoke(this.RootPath, "config reset.quiet true"); 61 | GitProcess.Invoke(this.RootPath, "config status.aheadbehind false"); 62 | GitProcess.Invoke(this.RootPath, "config user.name \"Functional Test User\""); 63 | GitProcess.Invoke(this.RootPath, "config user.email \"functional@test.com\""); 64 | GitProcess.Invoke(this.RootPath, "remote add origin " + CachePath); 65 | this.Fetch(this.Commitish); 66 | GitProcess.Invoke(this.RootPath, "branch --set-upstream-to " + this.Commitish + " origin/" + this.Commitish); 67 | GitProcess.Invoke(this.RootPath, "checkout " + this.Commitish); 68 | GitProcess.Invoke(this.RootPath, "branch --unset-upstream"); 69 | } 70 | 71 | public void Fetch(string commitish) 72 | { 73 | GitProcess.Invoke(this.RootPath, "fetch origin " + commitish); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/FileSystemHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Scalar.FunctionalTests.Tools 5 | { 6 | public static class FileSystemHelpers 7 | { 8 | public static StringComparison PathComparison 9 | { 10 | get 11 | { 12 | return CaseSensitiveFileSystem ? 13 | StringComparison.Ordinal : 14 | StringComparison.OrdinalIgnoreCase; 15 | } 16 | } 17 | 18 | public static StringComparer PathComparer 19 | { 20 | get 21 | { 22 | return CaseSensitiveFileSystem ? 23 | StringComparer.Ordinal : 24 | StringComparer.OrdinalIgnoreCase; 25 | } 26 | } 27 | 28 | public static bool CaseSensitiveFileSystem 29 | { 30 | get 31 | { 32 | return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/GitHelpers.cs: -------------------------------------------------------------------------------- 1 | using Scalar.Tests.Should; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading; 6 | 7 | namespace Scalar.FunctionalTests.Tools 8 | { 9 | public static class GitHelpers 10 | { 11 | private const string WindowsPathSeparator = "\\"; 12 | private const string GitPathSeparator = "/"; 13 | 14 | // A command sequence number added to the Trace2 stream to help tie the control 15 | // and the corresponding enlistment commands together. 16 | private static int TraceCommandSequenceId = 0; 17 | 18 | public static string ConvertPathToGitFormat(string relativePath) 19 | { 20 | return relativePath.Replace(WindowsPathSeparator, GitPathSeparator); 21 | } 22 | 23 | public static void CheckGitCommand(string virtualRepoRoot, string command, params string[] expectedLinesInResult) 24 | { 25 | ProcessResult result = GitProcess.InvokeProcess(virtualRepoRoot, command); 26 | result.Errors.ShouldBeEmpty(); 27 | foreach (string line in expectedLinesInResult) 28 | { 29 | result.Output.ShouldContain(line); 30 | } 31 | } 32 | 33 | public static void CheckGitCommandAgainstScalarRepo(string virtualRepoRoot, string command, params string[] expectedLinesInResult) 34 | { 35 | ProcessResult result = InvokeGitAgainstScalarRepo(virtualRepoRoot, command); 36 | result.Errors.ShouldBeEmpty(); 37 | foreach (string line in expectedLinesInResult) 38 | { 39 | result.Output.ShouldContain(line); 40 | } 41 | } 42 | 43 | public static ProcessResult InvokeGitAgainstScalarRepo( 44 | string scalarRepoRoot, 45 | string command, 46 | Dictionary environmentVariables = null, 47 | string input = null) 48 | { 49 | ProcessResult result = GitProcess.InvokeProcess(scalarRepoRoot, command, input, environmentVariables); 50 | string errors = result.Errors; 51 | 52 | return new ProcessResult( 53 | result.Output, 54 | errors, 55 | result.ExitCode); 56 | } 57 | 58 | public static void ValidateGitCommand( 59 | ScalarFunctionalTestEnlistment enlistment, 60 | ControlGitRepo controlGitRepo, 61 | string command, 62 | params object[] args) 63 | { 64 | command = string.Format(command, args); 65 | string controlRepoRoot = controlGitRepo.RootPath; 66 | string scalarRepoRoot = enlistment.RepoRoot; 67 | int pair_id = Interlocked.Increment(ref TraceCommandSequenceId); 68 | 69 | Dictionary environmentVariables = new Dictionary(); 70 | environmentVariables["GIT_QUIET"] = "true"; 71 | environmentVariables["GIT_COMMITTER_DATE"] = "Thu Feb 16 10:07:35 2017 -0700"; 72 | environmentVariables["XXX_SEQUENCE_ID"] = pair_id.ToString(); 73 | 74 | ProcessResult expectedResult = GitProcess.InvokeProcess(controlRepoRoot, command, environmentVariables); 75 | ProcessResult actualResult = GitHelpers.InvokeGitAgainstScalarRepo(scalarRepoRoot, command, environmentVariables); 76 | 77 | LinesShouldMatch(command + " Errors Lines", expectedResult.Errors, actualResult.Errors); 78 | LinesShouldMatch(command + " Output Lines", expectedResult.Output, actualResult.Output); 79 | 80 | if (command != "status") 81 | { 82 | ValidateGitCommand(enlistment, controlGitRepo, "status"); 83 | } 84 | } 85 | 86 | public static void LinesShouldMatch(string message, string expected, string actual) 87 | { 88 | IEnumerable actualLines = NonEmptyLines(actual); 89 | IEnumerable expectedLines = NonEmptyLines(expected); 90 | actualLines.ShouldMatchInOrder(expectedLines, LinesAreEqual, message); 91 | } 92 | 93 | private static IEnumerable NonEmptyLines(string data) 94 | { 95 | return data 96 | .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) 97 | .Where(s => !string.IsNullOrWhiteSpace(s)) 98 | .Where(s => !s.Contains("gvfs-helper error: '(curl")); 99 | } 100 | 101 | private static bool LinesAreEqual(string actualLine, string expectedLine) 102 | { 103 | return actualLine.Equals(expectedLine); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/GitProcess.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace Scalar.FunctionalTests.Tools 8 | { 9 | public static class GitProcess 10 | { 11 | public static string Invoke(string executionWorkingDirectory, string command) 12 | { 13 | return InvokeProcess(executionWorkingDirectory, command).Output; 14 | } 15 | 16 | public static ProcessResult InvokeProcess(string executionWorkingDirectory, string command, string inputData, Dictionary environmentVariables = null) 17 | { 18 | if (inputData == null) 19 | { 20 | return InvokeProcess(executionWorkingDirectory, command, environmentVariables); 21 | } 22 | 23 | using (MemoryStream stream = new MemoryStream()) 24 | { 25 | using (StreamWriter writer = new StreamWriter(stream, Encoding.Default, bufferSize: 4096, leaveOpen: true)) 26 | { 27 | writer.Write(inputData); 28 | } 29 | 30 | stream.Position = 0; 31 | 32 | return InvokeProcess(executionWorkingDirectory, command, environmentVariables, stream); 33 | } 34 | } 35 | 36 | public static ProcessResult InvokeProcess(string executionWorkingDirectory, string command, Dictionary environmentVariables = null, Stream inputStream = null) 37 | { 38 | ProcessStartInfo processInfo = new ProcessStartInfo(ScalarTestConfig.PathToGit); 39 | processInfo.WorkingDirectory = executionWorkingDirectory; 40 | processInfo.UseShellExecute = false; 41 | processInfo.RedirectStandardOutput = true; 42 | processInfo.RedirectStandardError = true; 43 | processInfo.Arguments = command; 44 | 45 | if (inputStream != null) 46 | { 47 | processInfo.RedirectStandardInput = true; 48 | } 49 | 50 | processInfo.EnvironmentVariables["GIT_TERMINAL_PROMPT"] = "0"; 51 | 52 | // Add some test-specific info to the Trace2 stream to help us 53 | // identify which TestCase is running. GIT_TRACE2_ENV_VARS 54 | // takes a comma-separated list of all of the environment 55 | // variables we are interested in. 56 | // 57 | // [1] "XXX_TEST_FULLNAME" -- The full name of the current 58 | // TestCase (along with the test parameters). 59 | // [2] "XXX_SEQUENCE_ID" -- The sequence-id for 60 | // control-vs-enlistment command pairs. (This env var 61 | // is set in Scalar.FunctionalTests/Tools/GitHelpers.cs 62 | // when applicable.) 63 | // 64 | // Notes: 65 | // [a] This only catches Git commands from the functional test 66 | // harness and NOT Git commands from GitAPI. See 67 | // `Scalar.FunctionalTests/Tools/GitProcess.cs` and 68 | // `Scalar.Common/Git/GitProcess.cs`. 69 | // 70 | // [b] This Trace2 decoration may introduce a little confusion 71 | // for fsmonitor--daemon.exe instances that are implicitly 72 | // spawned by fsmonitor_query_daemon() in a client process 73 | // and that persist for the duration of a multi-test-case 74 | // fixture. (The daemon process will (correctly) inherit 75 | // the env vars from the spawning client test, but since 76 | // it is long-running it may later talk to other test case 77 | // clients (which is correct, but confusing.)) 78 | // 79 | processInfo.EnvironmentVariables["XXX_TEST_FULLNAME"] = TestContext.CurrentContext.Test.FullName; 80 | processInfo.EnvironmentVariables["GIT_TRACE2_ENV_VARS"] = "XXX_TEST_FULLNAME,XXX_SEQUENCE_ID"; 81 | 82 | if (environmentVariables != null) 83 | { 84 | foreach (string key in environmentVariables.Keys) 85 | { 86 | processInfo.EnvironmentVariables[key] = environmentVariables[key]; 87 | } 88 | } 89 | 90 | return ProcessHelper.Run(processInfo, inputStream: inputStream); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/ProcessHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace Scalar.FunctionalTests.Tools 8 | { 9 | public static class ProcessHelper 10 | { 11 | public static ProcessResult Run(string fileName, string arguments) 12 | { 13 | ProcessStartInfo startInfo = new ProcessStartInfo(); 14 | startInfo.UseShellExecute = false; 15 | startInfo.RedirectStandardOutput = true; 16 | startInfo.RedirectStandardError = true; 17 | startInfo.CreateNoWindow = true; 18 | startInfo.FileName = fileName; 19 | startInfo.Arguments = arguments; 20 | 21 | return Run(startInfo); 22 | } 23 | 24 | public static ProcessResult Run(ProcessStartInfo processInfo, string errorMsgDelimeter = "\r\n", object executionLock = null, Stream inputStream = null) 25 | { 26 | using (Process executingProcess = new Process()) 27 | { 28 | string output = string.Empty; 29 | string errors = string.Empty; 30 | 31 | // From https://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx 32 | // To avoid deadlocks, use asynchronous read operations on at least one of the streams. 33 | // Do not perform a synchronous read to the end of both redirected streams. 34 | executingProcess.StartInfo = processInfo; 35 | executingProcess.ErrorDataReceived += (sender, args) => 36 | { 37 | if (args.Data != null) 38 | { 39 | errors = errors + args.Data + errorMsgDelimeter; 40 | } 41 | }; 42 | 43 | if (executionLock != null) 44 | { 45 | lock (executionLock) 46 | { 47 | output = StartProcess(executingProcess, inputStream); 48 | } 49 | } 50 | else 51 | { 52 | output = StartProcess(executingProcess, inputStream); 53 | } 54 | 55 | return new ProcessResult(output.ToString(), errors.ToString(), executingProcess.ExitCode); 56 | } 57 | } 58 | 59 | public static string GetProgramLocation(string processName) 60 | { 61 | ProcessResult result = ProcessHelper.Run(GetProgramLocator(), processName); 62 | if (result.ExitCode != 0) 63 | { 64 | return null; 65 | } 66 | 67 | string firstPath = 68 | string.IsNullOrWhiteSpace(result.Output) 69 | ? null 70 | : result.Output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); 71 | if (firstPath == null) 72 | { 73 | return null; 74 | } 75 | 76 | try 77 | { 78 | return Path.GetDirectoryName(firstPath); 79 | } 80 | catch (IOException) 81 | { 82 | return null; 83 | } 84 | } 85 | 86 | private static string GetProgramLocator() 87 | { 88 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 89 | { 90 | return "where"; 91 | } 92 | else 93 | { 94 | return "which"; 95 | } 96 | } 97 | 98 | private static string StartProcess(Process executingProcess, Stream inputStream = null) 99 | { 100 | executingProcess.Start(); 101 | 102 | if (inputStream != null) 103 | { 104 | inputStream.CopyTo(executingProcess.StandardInput.BaseStream); 105 | executingProcess.StandardInput.BaseStream.Close(); 106 | } 107 | 108 | if (executingProcess.StartInfo.RedirectStandardError) 109 | { 110 | executingProcess.BeginErrorReadLine(); 111 | } 112 | 113 | string output = string.Empty; 114 | if (executingProcess.StartInfo.RedirectStandardOutput) 115 | { 116 | output = executingProcess.StandardOutput.ReadToEnd(); 117 | } 118 | 119 | executingProcess.WaitForExit(); 120 | 121 | return output; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/ProcessResult.cs: -------------------------------------------------------------------------------- 1 | namespace Scalar.FunctionalTests.Tools 2 | { 3 | public class ProcessResult 4 | { 5 | public ProcessResult(string output, string errors, int exitCode) 6 | { 7 | this.Output = output; 8 | this.Errors = errors; 9 | this.ExitCode = exitCode; 10 | } 11 | 12 | public string Output { get; } 13 | public string Errors { get; } 14 | public int ExitCode { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/RepositoryHelpers.cs: -------------------------------------------------------------------------------- 1 | using Scalar.FunctionalTests.FileSystemRunners; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Scalar.FunctionalTests.Tools 5 | { 6 | public static class RepositoryHelpers 7 | { 8 | public static void DeleteTestDirectory(string repoPath) 9 | { 10 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 11 | { 12 | // Use cmd.exe to delete the enlistment as it properly handles tombstones and reparse points 13 | CmdRunner.DeleteDirectoryWithLimitedRetries(repoPath); 14 | } 15 | else 16 | { 17 | BashRunner.DeleteDirectoryWithLimitedRetries(repoPath); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/ScalarHelpers.cs: -------------------------------------------------------------------------------- 1 | using Scalar.Tests.Should; 2 | 3 | namespace Scalar.FunctionalTests.Tools 4 | { 5 | public static class ScalarHelpers 6 | { 7 | public const string GitConfigObjectCache = "gvfs.sharedCache"; 8 | 9 | public static string GetObjectsRootFromGitConfig(string repoRoot) 10 | { 11 | ProcessResult result = GitProcess.InvokeProcess(repoRoot, $"config --local {ScalarHelpers.GitConfigObjectCache}"); 12 | result.ExitCode.ShouldEqual(0, $"Failed to read git object root from config, error: {result.ExitCode}"); 13 | string.IsNullOrWhiteSpace(result.Output).ShouldBeFalse($"{ScalarHelpers.GitConfigObjectCache} should be set"); 14 | return result.Output.TrimEnd('\n'); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Scalar.FunctionalTests/Tools/TestConstants.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Scalar.FunctionalTests.Tools 4 | { 5 | public static class TestConstants 6 | { 7 | public const char GitPathSeparator = '/'; 8 | public const string DefaultScalarCacheFolderName = ".scalarCache"; 9 | 10 | public static class POSIXPlatform 11 | { 12 | public static class EnvironmentVariables 13 | { 14 | public const string LocalUserFolder = "HOME"; 15 | } 16 | } 17 | 18 | public static class LinuxPlatform 19 | { 20 | public static class EnvironmentVariables 21 | { 22 | public const string LocalUserCacheFolder = "XDG_CACHE_HOME"; 23 | } 24 | 25 | public const string LocalScalarFolderName = "scalar"; 26 | 27 | public static readonly string LocalScalarCachePath = Path.Combine(".cache", LocalScalarFolderName); 28 | } 29 | 30 | public static class DotGit 31 | { 32 | public const string Root = ".git"; 33 | public static readonly string Head = Path.Combine(DotGit.Root, "HEAD"); 34 | 35 | public static class Objects 36 | { 37 | public static readonly string Root = Path.Combine(DotGit.Root, "objects"); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Scalar.MSBuild/CompileTemplatedFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Build.Framework; 3 | using Microsoft.Build.Utilities; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace Scalar.MSBuild 9 | { 10 | public class CompileTemplatedFile : Task 11 | { 12 | [Required] 13 | public ITaskItem Template { get; set; } 14 | 15 | [Required] 16 | public string OutputFile { get; set; } 17 | 18 | [Output] 19 | public ITaskItem CompiledTemplate { get; set; } 20 | 21 | public override bool Execute() 22 | { 23 | string templateFilePath = this.Template.ItemSpec; 24 | IDictionary properties = ParseProperties(this.Template.GetMetadata("Properties")); 25 | 26 | string outputFileDirectory = Path.GetDirectoryName(this.OutputFile); 27 | 28 | if (!File.Exists(templateFilePath)) 29 | { 30 | this.Log.LogError("Failed to find template file '{0}'.", templateFilePath); 31 | return false; 32 | } 33 | 34 | // Copy the template to the destination to keep the same file mode bits/ACLs as the template 35 | File.Copy(templateFilePath, this.OutputFile, true); 36 | 37 | this.Log.LogMessage(MessageImportance.Low, "Reading template contents"); 38 | string template = File.ReadAllText(this.OutputFile); 39 | 40 | this.Log.LogMessage(MessageImportance.Normal, "Compiling template '{0}'", templateFilePath); 41 | string compiled = Compile(template, properties); 42 | 43 | if (!Directory.Exists(outputFileDirectory)) 44 | { 45 | this.Log.LogMessage(MessageImportance.Low, "Creating output directory '{0}'", outputFileDirectory); 46 | Directory.CreateDirectory(outputFileDirectory); 47 | } 48 | 49 | this.Log.LogMessage(MessageImportance.Normal, "Writing compiled template to '{0}'", this.OutputFile); 50 | File.WriteAllText(this.OutputFile, compiled); 51 | 52 | this.CompiledTemplate = new TaskItem(this.OutputFile, this.Template.CloneCustomMetadata()); 53 | 54 | return true; 55 | } 56 | 57 | private IDictionary ParseProperties(string propertiesStr) 58 | { 59 | string[] properties = propertiesStr?.Split(';') ?? new string[0]; 60 | var dict = new Dictionary(); 61 | 62 | foreach (string propertyStr in properties) 63 | { 64 | string[] kvp = propertyStr.Split(new[] {'='}, count: 2); 65 | if (kvp.Length > 1) 66 | { 67 | string key = kvp[0].Trim(); 68 | dict[key] = kvp[1].Trim(); 69 | } 70 | } 71 | 72 | return dict; 73 | } 74 | 75 | private string Compile(string template, IDictionary properties) 76 | { 77 | var sb = new StringBuilder(template); 78 | 79 | foreach (var kvp in properties) 80 | { 81 | this.Log.LogMessage(MessageImportance.Low, "Replacing \"{0}\" -> \"{1}\"", kvp.Key, kvp.Value); 82 | sb.Replace(kvp.Key, kvp.Value); 83 | } 84 | 85 | return sb.ToString(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Scalar.MSBuild/GenerateScalarConstants.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Framework; 2 | using Microsoft.Build.Utilities; 3 | using System.IO; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Scalar.MSBuild 7 | { 8 | public class GenerateScalarConstants : Task 9 | { 10 | [Required] 11 | public string MinimumGitVersion { get; set; } 12 | 13 | [Required] 14 | public string ExternalBinariesDirectoryName { get; set; } 15 | 16 | [Required] 17 | public string OutputFile { get; set; } 18 | 19 | public override bool Execute() 20 | { 21 | this.Log.LogMessage(MessageImportance.Normal, "Creating Scalar constants file with minimum Git version '{0}' at '{1}'...", this.MinimumGitVersion, this.OutputFile); 22 | 23 | if (!TryParseVersion(this.MinimumGitVersion, out var version)) 24 | { 25 | this.Log.LogError("Failed to parse Git version '{0}'.", this.MinimumGitVersion); 26 | return false; 27 | } 28 | 29 | string outputDirectory = Path.GetDirectoryName(this.OutputFile); 30 | if (!Directory.Exists(outputDirectory)) 31 | { 32 | Directory.CreateDirectory(outputDirectory); 33 | } 34 | 35 | string template = 36 | @"// 37 | // This file is auto-generated by Scalar.Build.GenerateScalarConstants. 38 | // Any changes made directly in this file will be lost. 39 | // 40 | using Scalar.Common.Git; 41 | 42 | namespace Scalar.Common 43 | {{ 44 | public static partial class ScalarConstants 45 | {{ 46 | public static readonly GitVersion SupportedGitVersion = new GitVersion({0}, {1}, {2}, ""{3}"", {4}, {5}); 47 | public const string ExternalBinariesDirectoryName = ""{6}""; 48 | }} 49 | }}"; 50 | 51 | File.WriteAllText( 52 | this.OutputFile, 53 | string.Format( 54 | template, 55 | version.Major, 56 | version.Minor, 57 | version.Build, 58 | version.Platform, 59 | version.Revision, 60 | version.MinorRevision, 61 | ExternalBinariesDirectoryName)); 62 | 63 | return true; 64 | } 65 | 66 | private static bool TryParseVersion(string versionString, out GitVersion version) 67 | { 68 | const string pattern = @"(\d+)\.(\d+)\.(\d+)\.([A-Z]+)\.(\d+)\.(\d+)"; 69 | 70 | Match match = Regex.Match(versionString, pattern, RegexOptions.IgnoreCase); 71 | if (match.Success) 72 | { 73 | version = new GitVersion 74 | { 75 | Major = int.Parse(match.Groups[1].Value), 76 | Minor = int.Parse(match.Groups[2].Value), 77 | Build = int.Parse(match.Groups[3].Value), 78 | Platform = match.Groups[4].Value, 79 | Revision = int.Parse(match.Groups[5].Value), 80 | MinorRevision = int.Parse(match.Groups[6].Value) 81 | }; 82 | 83 | return true; 84 | } 85 | 86 | version = default(GitVersion); 87 | return false; 88 | } 89 | 90 | private struct GitVersion 91 | { 92 | public int Major { get; set; } 93 | public int Minor { get; set; } 94 | public int Build { get; set; } 95 | public string Platform { get; set; } 96 | public int Revision { get; set; } 97 | public int MinorRevision { get; set; } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Scalar.MSBuild/GenerateWindowsAppManifest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Framework; 2 | using Microsoft.Build.Utilities; 3 | using System.IO; 4 | 5 | namespace Scalar.MSBuild 6 | { 7 | public class GenerateWindowsAppManifest : Task 8 | { 9 | [Required] 10 | public string Version { get; set; } 11 | 12 | [Required] 13 | public string ApplicationName { get; set; } 14 | 15 | [Required] 16 | public string OutputFile { get; set; } 17 | 18 | public override bool Execute() 19 | { 20 | this.Log.LogMessage(MessageImportance.Normal, "Creating application manifest file for '{0}'...", this.ApplicationName); 21 | 22 | string manifestDirectory = Path.GetDirectoryName(this.OutputFile); 23 | if (!Directory.Exists(manifestDirectory)) 24 | { 25 | Directory.CreateDirectory(manifestDirectory); 26 | } 27 | 28 | // Any application that calls GetVersionEx must have an application manifest in order to get an accurate response. 29 | // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx for details 30 | File.WriteAllText( 31 | this.OutputFile, 32 | string.Format( 33 | @" 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ", 44 | this.Version, 45 | this.ApplicationName)); 46 | 47 | return true; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Scalar.MSBuild/Scalar.MSBuild.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Scalar.MSBuild/Scalar.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(IntermediateOutputPath)app.manifest 8 | 9 | 10 | 11 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Scalar.MSBuild/Scalar.tasks: -------------------------------------------------------------------------------- 1 | 2 | 3 | <_TaskAssembly>$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll 4 | <_TaskFactory>CodeTaskFactory 5 | 6 | 7 | <_TaskAssembly>$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll 8 | <_TaskFactory>RoslynCodeTaskFactory 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Scalar.Signing/PostSignFiles.Mac.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | IF "%1"=="" ( 4 | ECHO "Missing signing root directory argument" 5 | EXIT /B 1 6 | ) ELSE (SET "SIGNDIR=%1") 7 | 8 | IF "%2"=="" ( 9 | ECHO "Missing layout directory argument" 10 | EXIT /B 1 11 | ) ELSE (SET "LAYOUTDIR=%2") 12 | 13 | echo Copying signed files... 14 | 15 | xcopy "%SIGNDIR%\pe\scalar.dll" "%LAYOUTDIR%\usr\local\scalar\" /k/h/y 16 | xcopy "%SIGNDIR%\pe\scalar.common.dll" "%LAYOUTDIR%\usr\local\scalar\" /k/h/y 17 | xcopy "%SIGNDIR%\macho\scalar" "%LAYOUTDIR%\usr\local\scalar\" /k/h/y 18 | -------------------------------------------------------------------------------- /Scalar.Signing/PreSignFiles.Mac.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | IF "%1"=="" ( 4 | ECHO "Missing layout directory argument" 5 | EXIT /B 1 6 | ) ELSE (SET "LAYOUTDIR=%1") 7 | 8 | IF "%2"=="" ( 9 | ECHO "Missing signing root directory argument" 10 | EXIT /B 1 11 | ) ELSE (SET "SIGNDIR=%2") 12 | 13 | echo Copying files to sign... 14 | 15 | rmdir /s /q "%SIGNDIR%" > nul 2>&1 16 | mkdir "%SIGNDIR%\pe" 17 | mkdir "%SIGNDIR%\macho" 18 | xcopy "%LAYOUTDIR%\usr\local\scalar\scalar.dll" "%SIGNDIR%\pe" /k/h/y 19 | xcopy "%LAYOUTDIR%\usr\local\scalar\scalar.common.dll" "%SIGNDIR%\pe" /k/h/y 20 | xcopy "%LAYOUTDIR%\usr\local\scalar\scalar" "%SIGNDIR%\macho" /k/h/y 21 | -------------------------------------------------------------------------------- /Scalar.Signing/Scalar.SignFiles.Mac.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | $(RootDir) 8 | $(OutDir)\macho\ 9 | 10 | 11 | 12 | 15 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Scalar.Signing/Scalar.SignFiles.Windows.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | $(RootDir) 8 | 9 | 10 | 11 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Scalar.Signing/Scalar.SignInstaller.Windows.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | $(RootDir) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Scalar.Signing/Scalar.SignPackage.Mac.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | $(RootDir) 8 | $(OutDir)\ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Scalar.Signing/notarize-pkg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file was based on https://github.com/microsoft/BuildXL/blob/8c2348ff04e6ca78726bb945fb2a0f6a55a5c7d6/Private/macOS/notarize.sh 4 | # 5 | # For detailed explanation see: https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution/customizing_the_notarization_workflow 6 | 7 | usage() { 8 | cat < -p -pkg 11 | -id or --appleid # A valid Apple ID email address, account must have correct certificates available 12 | -p or --password # The password for the specified Apple ID or Apple One-Time password (to avoid 2FA) 13 | -pkg or --package # The path to an already signed meta/flat-package 14 | -b or --bundleid # Primary bundle identifier 15 | EOM 16 | exit 0 17 | } 18 | 19 | declare arg_AppleId="" 20 | declare arg_Password="" 21 | declare arg_PackagePath="" 22 | 23 | [ $# -eq 0 ] && { usage; } 24 | 25 | function parseArgs() { 26 | arg_Positional=() 27 | while [[ $# -gt 0 ]]; do 28 | cmd="$1" 29 | case $cmd in 30 | --help | -h) 31 | usage 32 | shift 33 | exit 0 34 | ;; 35 | --appleid | -id) 36 | arg_AppleId=$2 37 | shift 38 | ;; 39 | --password | -p) 40 | arg_Password="$2" 41 | shift 42 | ;; 43 | --package | -pkg) 44 | arg_PackagePath="$2" 45 | shift 46 | ;; 47 | --bundledid | -b) 48 | arg_BundleId="$2" 49 | shift 50 | ;; 51 | *) 52 | arg_Positional+=("$1") 53 | shift 54 | ;; 55 | esac 56 | done 57 | } 58 | 59 | parseArgs "$@" 60 | 61 | if [[ -z $arg_AppleId ]]; then 62 | echo "[ERROR] Must supply valid / non-empty Apple ID!" 63 | exit 1 64 | fi 65 | 66 | if [[ -z $arg_Password ]]; then 67 | echo "[ERROR] Must supply valid / non-empty password!" 68 | exit 1 69 | fi 70 | 71 | if [[ ! -f $arg_PackagePath ]]; then 72 | echo "[ERROR] Must supply valid / non-empty path to package!" 73 | exit 1 74 | fi 75 | 76 | if [[ -z $arg_BundleId ]]; then 77 | echo "[ERROR] Must supply valid / non-empty primary bundle identifier" 78 | exit 1 79 | fi 80 | 81 | echo "Notarizating $arg_PackagePath" 82 | 83 | echo -e "Current state:\n" 84 | xcrun stapler validate -v "$arg_PackagePath" 85 | 86 | if [[ $? -eq 0 ]]; then 87 | echo "$arg_PackagePath already notarized and stapled, nothing to do!" 88 | exit 0 89 | fi 90 | 91 | set -e 92 | 93 | declare start_time=$(date +%s) 94 | 95 | declare output="/tmp/progress.xml" 96 | 97 | echo "Uploading package to notarization service, please wait..." 98 | xcrun altool --notarize-app -t osx -f $arg_PackagePath --primary-bundle-id $arg_BundleId -u $arg_AppleId -p $arg_Password --output-format xml | tee $output 99 | 100 | declare request_id=$(/usr/libexec/PlistBuddy -c "print :notarization-upload:RequestUUID" $output) 101 | 102 | echo "Checking notarization request validity..." 103 | if [[ $request_id =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]]; then 104 | declare attempts=5 105 | 106 | while : 107 | do 108 | echo "Waiting a bit before checking on notarization status again..." 109 | 110 | sleep 20 111 | xcrun altool --notarization-info $request_id -u $arg_AppleId -p $arg_Password --output-format xml | tee $output 112 | 113 | declare status=$(/usr/libexec/PlistBuddy -c "print :notarization-info:Status" $output) 114 | echo "Status: $status" 115 | 116 | if [[ -z $status ]]; then 117 | echo "Left attempts: $attempts" 118 | 119 | if (($attempts <= 0)); then 120 | break 121 | fi 122 | 123 | ((attempts--)) 124 | else 125 | if [[ $status != "in progress" ]]; then 126 | break 127 | fi 128 | fi 129 | done 130 | 131 | declare end_time=$(date +%s) 132 | echo -e "Completed in $(($end_time-$start_time)) seconds\n" 133 | 134 | if [[ "$status" != "success" ]]; then 135 | echo "Error notarizing, exiting..." >&2 136 | exit 1 137 | else 138 | declare url=$(/usr/libexec/PlistBuddy -c "print :notarization-info:LogFileURL" $output) 139 | 140 | if [ "$url" ]; then 141 | curl $url 142 | fi 143 | 144 | # Staple the ticket to the package 145 | xcrun stapler staple "$arg_PackagePath" 146 | 147 | echo -e "State after notarization:\n" 148 | xcrun stapler validate -v "$arg_PackagePath" 149 | echo -e "Stapler exit code: $? (must be zero on success!)\n" 150 | fi 151 | else 152 | echo "Invalid request id found in 'altool' output, aborting!" >&2 153 | exit 1 154 | fi 155 | -------------------------------------------------------------------------------- /Scalar.TestInfrastructure/DataSources.cs: -------------------------------------------------------------------------------- 1 | namespace Scalar.Tests 2 | { 3 | public class DataSources 4 | { 5 | public static object[] AllBools 6 | { 7 | get 8 | { 9 | return new object[] 10 | { 11 | new object[] { true }, 12 | new object[] { false }, 13 | }; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Scalar.TestInfrastructure/Scalar.TestInfrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Scalar.TestInfrastructure/Should/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Scalar.Tests.Should 8 | { 9 | public static class StringExtensions 10 | { 11 | public static string Repeat(this string self, int count) 12 | { 13 | return string.Join(string.Empty, Enumerable.Range(0, count).Select(x => self).ToArray()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Scalar.TestInfrastructure/Should/StringShouldExtensions.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | 4 | namespace Scalar.Tests.Should 5 | { 6 | public static class StringShouldExtensions 7 | { 8 | public static int ShouldBeAnInt(this string value, string message) 9 | { 10 | int output; 11 | Assert.IsTrue(int.TryParse(value, out output), message); 12 | return output; 13 | } 14 | 15 | public static string ShouldContain(this string actualValue, params string[] expectedSubstrings) 16 | { 17 | foreach (string expectedSubstring in expectedSubstrings) 18 | { 19 | Assert.IsTrue( 20 | actualValue.Contains(expectedSubstring), 21 | "Expected substring '{0}' not found in '{1}'", 22 | expectedSubstring, 23 | actualValue); 24 | } 25 | 26 | return actualValue; 27 | } 28 | 29 | public static string ShouldNotContain(this string actualValue, bool ignoreCase, params string[] unexpectedSubstrings) 30 | { 31 | foreach (string unexpectedSubstring in unexpectedSubstrings) 32 | { 33 | if (ignoreCase) 34 | { 35 | Assert.IsFalse( 36 | actualValue.IndexOf(unexpectedSubstring, 0, StringComparison.OrdinalIgnoreCase) >= 0, 37 | "Unexpected substring '{0}' found in '{1}'", 38 | unexpectedSubstring, 39 | actualValue); 40 | } 41 | else 42 | { 43 | Assert.IsFalse( 44 | actualValue.Contains(unexpectedSubstring), 45 | "Unexpected substring '{0}' found in '{1}'", 46 | unexpectedSubstring, 47 | actualValue); 48 | } 49 | } 50 | 51 | return actualValue; 52 | } 53 | 54 | public static string ShouldContainOneOf(this string actualValue, params string[] expectedSubstrings) 55 | { 56 | for (int i = 0; i < expectedSubstrings.Length; i++) 57 | { 58 | if (actualValue.Contains(expectedSubstrings[i])) 59 | { 60 | return actualValue; 61 | } 62 | } 63 | 64 | Assert.Fail("No expected substrings found in '{0}'", actualValue); 65 | return actualValue; 66 | } 67 | 68 | public static string ShouldNotBeNullOrEmpty(this string value) 69 | { 70 | Assert.IsFalse(string.IsNullOrEmpty(value)); 71 | return value; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Scalar.TestInfrastructure/Should/ValueShouldExtensions.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | 4 | namespace Scalar.Tests.Should 5 | { 6 | public static class ValueShouldExtensions 7 | { 8 | public static bool ShouldBeTrue(this bool actualValue, string message = "") 9 | { 10 | actualValue.ShouldEqual(true, message); 11 | return actualValue; 12 | } 13 | 14 | public static bool ShouldBeFalse(this bool actualValue, string message = "") 15 | { 16 | actualValue.ShouldEqual(false, message); 17 | return actualValue; 18 | } 19 | 20 | public static T ShouldBeAtLeast(this T actualValue, T expectedValue, string message = "") where T : IComparable 21 | { 22 | Assert.GreaterOrEqual(actualValue, expectedValue, message); 23 | return actualValue; 24 | } 25 | 26 | public static T ShouldBeAtMost(this T actualValue, T expectedValue, string message = "") where T : IComparable 27 | { 28 | Assert.LessOrEqual(actualValue, expectedValue, message); 29 | return actualValue; 30 | } 31 | 32 | public static T ShouldEqual(this T actualValue, T expectedValue, string message = "") 33 | { 34 | Assert.AreEqual(expectedValue, actualValue, message); 35 | return actualValue; 36 | } 37 | 38 | public static T[] ShouldEqual(this T[] actualValue, T[] expectedValue, int start, int count) 39 | { 40 | expectedValue.Length.ShouldBeAtLeast(start + count); 41 | for (int i = 0; i < count; ++i) 42 | { 43 | actualValue[i].ShouldEqual(expectedValue[i + start]); 44 | } 45 | 46 | return actualValue; 47 | } 48 | 49 | public static T ShouldNotEqual(this T actualValue, T unexpectedValue, string message = "") 50 | { 51 | Assert.AreNotEqual(unexpectedValue, actualValue, message); 52 | return actualValue; 53 | } 54 | 55 | public static T ShouldBeSameAs(this T actualValue, T expectedValue, string message = "") 56 | { 57 | Assert.AreSame(expectedValue, actualValue, message); 58 | return actualValue; 59 | } 60 | 61 | public static T ShouldNotBeSameAs(this T actualValue, T expectedValue, string message = "") 62 | { 63 | Assert.AreNotSame(expectedValue, actualValue, message); 64 | return actualValue; 65 | } 66 | 67 | public static T ShouldBeOfType(this object obj) 68 | { 69 | Assert.IsTrue(obj is T, "Expected type {0}, but the object is actually of type {1}", typeof(T), obj.GetType()); 70 | return (T)obj; 71 | } 72 | 73 | public static void ShouldBeNull(this T obj, string message = "") 74 | where T : class 75 | { 76 | Assert.IsNull(obj, message); 77 | } 78 | 79 | public static T ShouldNotBeNull(this T obj, string message = "") 80 | where T : class 81 | { 82 | Assert.IsNotNull(obj, message); 83 | return obj; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Scalar.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Scalar.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30406.18 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EB9427BB-456A-4319-8916-E782E0F5F6D3}" 7 | ProjectSection(SolutionItems) = preProject 8 | Dependencies.props = Dependencies.props 9 | Directory.Build.props = Directory.Build.props 10 | Directory.Build.targets = Directory.Build.targets 11 | global.json = global.json 12 | EndProjectSection 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scalar.FunctionalTests", "Scalar.FunctionalTests\Scalar.FunctionalTests.csproj", "{C7E08779-6F45-4025-89E1-31346C9B234F}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scalar.TestInfrastructure", "Scalar.TestInfrastructure\Scalar.TestInfrastructure.csproj", "{77FC445D-FD03-4EE0-8582-7BD1437D9842}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scalar.MSBuild", "Scalar.MSBuild\Scalar.MSBuild.csproj", "{6B05E1DE-1C67-48F7-9A2A-E109D7252CF2}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {C7E08779-6F45-4025-89E1-31346C9B234F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {C7E08779-6F45-4025-89E1-31346C9B234F}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {C7E08779-6F45-4025-89E1-31346C9B234F}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {C7E08779-6F45-4025-89E1-31346C9B234F}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {77FC445D-FD03-4EE0-8582-7BD1437D9842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {77FC445D-FD03-4EE0-8582-7BD1437D9842}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {77FC445D-FD03-4EE0-8582-7BD1437D9842}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {77FC445D-FD03-4EE0-8582-7BD1437D9842}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {6B05E1DE-1C67-48F7-9A2A-E109D7252CF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {6B05E1DE-1C67-48F7-9A2A-E109D7252CF2}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {6B05E1DE-1C67-48F7-9A2A-E109D7252CF2}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {6B05E1DE-1C67-48F7-9A2A-E109D7252CF2}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(ExtensibilityGlobals) = postSolution 43 | SolutionGuid = {FDE61E04-DC84-437F-A176-40CD62CDE05F} 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /Scripts/BuildScalarForWindows.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SETLOCAL 3 | setlocal enabledelayedexpansion 4 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 5 | 6 | IF "%1"=="" (SET "Configuration=Debug") ELSE (SET "Configuration=%1") 7 | IF "%2"=="" (SET "ScalarVersion=0.2.173.2") ELSE (SET "ScalarVersion=%2") 8 | 9 | dotnet publish %SCALAR_SRCDIR%\Scalar.sln -p:ScalarVersion=%ScalarVersion% --configuration %Configuration% --runtime win10-x64 -v:n || exit /b 1 10 | 11 | ENDLOCAL 12 | -------------------------------------------------------------------------------- /Scripts/CI/CreateFTDrop.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\..\InitializeEnvironment.bat || EXIT /b 10 3 | 4 | IF "%1"=="" GOTO USAGE 5 | IF "%2"=="" GOTO USAGE 6 | 7 | SETLOCAL enableextensions 8 | SET Configuration=%1 9 | SET SCALAR_STAGEDIR=%2 10 | 11 | REM Prepare the staging directories for functional tests. 12 | IF EXIST %SCALAR_STAGEDIR% ( 13 | rmdir /s /q %SCALAR_STAGEDIR% 14 | ) 15 | 16 | SET scriptsSrc=%SCALAR_SCRIPTSDIR%\* 17 | SET testsSrc=%SCALAR_OUTPUTDIR%\Scalar.FunctionalTests\bin\%Configuration%\netcoreapp3.1\win10-x64\publish 18 | 19 | SET scriptsDest=%SCALAR_STAGEDIR%\src\Scripts 20 | SET testsDest=%SCALAR_STAGEDIR%\out\Scalar.FunctionalTests\bin\%Configuration%\netcoreapp3.1\win10-x64\publish 21 | 22 | mkdir %scriptsDest% 23 | mkdir %testsDest% 24 | 25 | REM Make a minimal 'test' enlistment to pass along our pipeline. 26 | xcopy %scriptsSrc% %scriptsDest% /S /Y || EXIT /B 1 27 | xcopy %testsSrc% %testsDest% /S /Y || EXIT /B 1 28 | GOTO END 29 | 30 | :USAGE 31 | echo "ERROR: Usage: CreateFTDrop.bat [configuration] [build drop root directory]" 32 | EXIT /b 1 33 | 34 | :END 35 | EXIT 0 36 | -------------------------------------------------------------------------------- /Scripts/CI/CreateInstallerDrop.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\..\InitializeEnvironment.bat || EXIT /B 10 3 | 4 | IF "%1"=="" GOTO USAGE 5 | IF "%2"=="" GOTO USAGE 6 | 7 | SETLOCAL enableextensions 8 | SET Configuration=%1 9 | SET SCALAR_STAGEDIR=%2 10 | 11 | REM Prepare the staging directories for installer drop. 12 | IF EXIST %SCALAR_STAGEDIR% ( 13 | rmdir /S /Q %SCALAR_STAGEDIR% 14 | ) 15 | 16 | mkdir %SCALAR_STAGEDIR% 17 | 18 | xcopy %SCALAR_OUTPUTDIR%\Scalar.Installer.Windows\dist\%Configuration%\* %SCALAR_STAGEDIR% /S /Y 19 | 20 | GOTO END 21 | 22 | :USAGE 23 | echo "ERROR: Usage: CreateInstallerDrop.bat [configuration] [build drop root directory]" 24 | EXIT /B 1 25 | 26 | :END 27 | EXIT 0 28 | -------------------------------------------------------------------------------- /Scripts/CI/Set-Version.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Generates the pipeline variables for the product version and set the build number. 3 | #> 4 | param ( 5 | [Parameter(Mandatory)] 6 | [string]$SourceBranchCounter, # should always be set to counter($(Build.SourceBranch), 0) 7 | [switch]$SetVariablesOnly # if set only set pipeline variables and do not update the build version 8 | ) 9 | 10 | function Set-PipelineVariable { 11 | Param ([string]$VarName, [string]$VarValue) 12 | Write-Host "##vso[task.setvariable variable=$VarName;]$VarValue" 13 | } 14 | 15 | function Set-BuildNumber { 16 | Param([string]$BuildNumber) 17 | if ($SetVariablesOnly) { 18 | Write-Host "Skipping Set-BuildNumber as the -SetVariablesOnly option is present." 19 | } else { 20 | Write-Host "##vso[build.updatebuildnumber]$BuildNumber" 21 | } 22 | } 23 | 24 | $SourceBranch = $Env:BUILD_SOURCEBRANCH 25 | $SourceBranchName = $Env:BUILD_SOURCEBRANCHNAME 26 | 27 | $ReleaseBranch = 'refs/heads/releases/(?\d{2})\.(?\d{2})\.(?\d{3})' 28 | $PullRequest = 'refs/pull/(?\d+)/merge' 29 | 30 | $Major = 0 31 | $Minor = 0 32 | $Revision = 0 33 | 34 | # Release scenario. Build number is taken from the branch name "year.month.milestone". 35 | if ($SourceBranch -match $ReleaseBranch) { 36 | $Major = $Matches["Year"] 37 | $Minor = $Matches["Month"] 38 | $Revision = $Matches["Milestone"] 39 | Set-BuildNumber "$Major.$Minor.$Revision.$SourceBranchCounter" 40 | } 41 | # PR scenario. Prefix with PR number with "PR-". 42 | elseif ($SourceBranch -match $PullRequest) { 43 | $Major = 10 44 | $Minor = 20 45 | $Revision = $Matches["PrNum"] 46 | Set-BuildNumber "PR-$Revision.$SourceBranchCounter" 47 | } 48 | # Typical CI scenario. Handles tags and non-release branches. Prefix "CI-" with the branch name. 49 | else { 50 | $Major = (Get-Date -Format yy) 51 | $Minor = (Get-Date -Format MM) 52 | $Revision = (Get-Date -Format dd) 53 | Set-BuildNumber "CI-$SourceBranchName.$SourceBranchCounter" 54 | } 55 | 56 | Set-PipelineVariable "majorAndMinorVersion" "$Major.$Minor" 57 | Set-PipelineVariable "revision" "$Revision.$SourceBranchCounter" 58 | Set-PipelineVariable "fullVersion" "$Major.$Minor.$Revision.$SourceBranchCounter" 59 | -------------------------------------------------------------------------------- /Scripts/CapturePerfView.bat: -------------------------------------------------------------------------------- 1 | @if not defined _echo echo off 2 | setlocal 3 | 4 | set filename=%~n0 5 | goto :parseargs 6 | 7 | :showhelp 8 | echo. 9 | echo Captures a system wide PerfView while running a command then compresses it into a zip. 10 | echo. 11 | echo USAGE: %filename% command 12 | echo. 13 | echo EXAMPLES: 14 | echo %filename% git status 15 | echo %filename% git fetch 16 | goto :end 17 | 18 | :parseargs 19 | if "%1" == "" goto :showhelp 20 | if /i "%1" == "/?" goto :showhelp 21 | if /i "%1" == "-?" goto :showhelp 22 | if /i "%1" == "/h" goto :showhelp 23 | if /i "%1" == "-h" goto :showhelp 24 | if /i "%1" == "/help" goto :showhelp 25 | if /i "%1" == "-help" goto :showhelp 26 | 27 | :: Find the given command on the path, then look for a .PDB file next to it 28 | :VerifyPDB 29 | set P2=.;%PATH% 30 | for %%e in (%PATHEXT%) do @for %%i in (%~n1%%e) do @if NOT "%%~$P2:i"=="" if NOT exist "%%~dpn$P2:i.pdb" ( 31 | echo Unable to locate PDB file %%~dpn$P2:i.pdb. Aborting %filename% 1>&2 32 | exit /B 1 33 | ) 34 | 35 | :VerifyPerfView 36 | where /q perfview || ( 37 | echo Please see the PerfView GitHub Download Page to download an up-to-date version 1>&2 38 | echo of PerfView and copy it to a directory in your path. 1>&2 39 | echo. 1>&2 40 | echo https://github.com/Microsoft/perfview/blob/master/documentation/Downloading.md 1>&2 41 | exit /B 2 42 | ) 43 | 44 | :: Generate output filenames 45 | if NOT "%_NTUSER%" == "" ( 46 | set perfviewfilename=%_NTUSER%-%~n1-%2 47 | ) ELSE ( 48 | if NOT "%USERNAME%" == "" ( 49 | set perfviewfilename=%USERNAME%-%~n1-%2 50 | ) ELSE ( 51 | set perfviewfilename=%~n1-%2 52 | ) 53 | ) 54 | set perfviewstartlog=%perfviewfilename%.start.log.txt 55 | set perfviewstoplog=%perfviewfilename%.end.log.txt 56 | 57 | :: Capture the perfview without requiring any human intervention 58 | :CapturePerfView 59 | echo Capture perf view for '%*'... 60 | perfview start /AcceptEULA /NoGui /NoNGenRundown /Merge /Zip /Providers:*Microsoft.Git.Scalar:@StacksEnabled=true,*Microsoft.Internal.Git.Plugin:@StacksEnabled=true,*Microsoft.OSGENG.Testing.GitMsWrapper:@StacksEnabled=true /kernelEvents=default+FileIOInit /logfile:"%perfviewstartlog%" "%perfviewfilename%" || goto :HandlePerfViewStartError 61 | echo. 62 | set STARTTIME=%TIME% 63 | %* 64 | set ENDTIME=%TIME% 65 | echo. 66 | CALL :PrintElapsedTime 67 | 68 | :: Merge perfview into ZIP file 69 | echo Merging and compressing perf view... 70 | perfview stop /AcceptEULA /NoGui /NoNGenRundown /Merge /Zip /Providers:*Microsoft.Git.Scalar:@StacksEnabled=true,*Microsoft.Internal.Git.Plugin:@StacksEnabled=true,*Microsoft.OSGENG.Testing.GitMsWrapper:@StacksEnabled=true /kernelEvents=default+FileIOInit /logfile:"%perfviewstoplog%" || goto :HandlePerfViewStopError 71 | CALL :CheckForFile 72 | echo PerfView trace can be found in "%perfviewfilename%.etl.zip" 73 | goto :end 74 | 75 | :HandlePerfViewStartError 76 | echo Could not start perfview, please see %perfviewstartlog% for details. 77 | EXIT /B 3 78 | 79 | :HandlePerfViewStopError 80 | echo Could not stop perfview, please see %perfviewstoplog% for details. 81 | EXIT /B 4 82 | 83 | :: Now wait for perfview to complete writing out the file 84 | :CheckForFile 85 | IF EXIST "%perfviewfilename%.etl.zip" EXIT /B 0 86 | TIMEOUT /T 1 >nul 87 | goto :CheckForFile 88 | 89 | :PrintElapsedTime 90 | :: Change formatting for the start and end times 91 | for /F "tokens=1-4 delims=:.," %%a in ("%STARTTIME%") do ( 92 | set /A "start=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100" 93 | ) 94 | 95 | for /F "tokens=1-4 delims=:.," %%a in ("%ENDTIME%") do ( 96 | set /A "end=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100" 97 | ) 98 | 99 | :: Calculate the elapsed time by subtracting values 100 | set /A elapsed=end-start 101 | 102 | :: Format the results for output 103 | set /A hh=elapsed/(60*60*100), rest=elapsed%%(60*60*100), mm=rest/(60*100), rest%%=60*100, ss=rest/100, cc=rest%%100 104 | if %hh% lss 10 set hh=0%hh% 105 | if %mm% lss 10 set mm=0%mm% 106 | if %ss% lss 10 set ss=0%ss% 107 | if %cc% lss 10 set cc=0%cc% 108 | 109 | set DURATION=%hh%:%mm%:%ss%.%cc% 110 | echo Command duration : %DURATION% 111 | EXIT /B 0 112 | 113 | :end 114 | endlocal 115 | -------------------------------------------------------------------------------- /Scripts/InitializeEnvironment.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Set environment variables for interesting paths that scripts might need access to. 4 | PUSHD %~dp0 5 | SET SCALAR_SCRIPTSDIR=%CD% 6 | POPD 7 | 8 | CALL :RESOLVEPATH "%SCALAR_SCRIPTSDIR%\.." 9 | SET SCALAR_SRCDIR=%_PARSED_PATH_% 10 | 11 | CALL :RESOLVEPATH "%SCALAR_SRCDIR%\.." 12 | SET SCALAR_ENLISTMENTDIR=%_PARSED_PATH_% 13 | 14 | SET SCALAR_OUTPUTDIR=%SCALAR_ENLISTMENTDIR%\out 15 | 16 | REM Clean up 17 | SET _PARSED_PATH_= 18 | 19 | GOTO :EOF 20 | 21 | :RESOLVEPATH 22 | SET "_PARSED_PATH_=%~f1" 23 | GOTO :EOF 24 | -------------------------------------------------------------------------------- /Scripts/Linux/BuildScalarForLinux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh" 4 | 5 | CONFIGURATION=$1 6 | if [ -z $CONFIGURATION ]; then 7 | CONFIGURATION=Debug 8 | fi 9 | 10 | VERSION=$2 11 | if [ -z $VERSION ]; then 12 | VERSION="0.2.173.2" 13 | fi 14 | 15 | ARCH=$(uname -m) 16 | if test "$ARCH" != "x86_64"; then 17 | >&2 echo "architecture must be x86_64 for struct stat; stopping" 18 | exit 1 19 | fi 20 | 21 | CC=${CC:-cc} 22 | 23 | echo 'main(){int i=1;const char *n="n";struct stat b;i=__xstat64(i,n,&b);}' | \ 24 | cc -xc -include sys/stat.h -o /dev/null - 2>/dev/null 25 | 26 | if test $? != 0; then 27 | >&2 echo "__xstat64() not found in libc ABI; stopping" 28 | exit 1 29 | fi 30 | 31 | # If we're building the Profiling(Release) configuration, remove Profiling() for building .NET code 32 | if [ "$CONFIGURATION" == "Profiling(Release)" ]; then 33 | CONFIGURATION=Release 34 | fi 35 | 36 | dotnet publish $SCALAR_SRCDIR/Scalar.sln --runtime linux-x64 -p:ScalarVersion=$VERSION --configuration $CONFIGURATION || exit 1 37 | -------------------------------------------------------------------------------- /Scripts/Linux/CleanupFunctionalTests.sh: -------------------------------------------------------------------------------- 1 | . "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh" 2 | 3 | pkill -9 -l Scalar.FunctionalTests 4 | pkill -9 -l git 5 | pkill -9 -l scalar 6 | 7 | if [ -d /Scalar.FT ]; then 8 | sudo rm -r /Scalar.FT 9 | fi 10 | -------------------------------------------------------------------------------- /Scripts/Linux/InitializeEnvironment.sh: -------------------------------------------------------------------------------- 1 | SCRIPTDIR="$(dirname ${BASH_SOURCE[0]})" 2 | 3 | # convert to an absolute path because it is required by `dotnet publish` 4 | pushd $SCRIPTDIR &>/dev/null 5 | export SCALAR_SCRIPTSDIR="$(pwd)" 6 | popd &>/dev/null 7 | 8 | export SCALAR_SRCDIR=$SCALAR_SCRIPTSDIR/../.. 9 | 10 | export SCALAR_ENLISTMENTDIR=$SCALAR_SRCDIR/.. 11 | export SCALAR_OUTPUTDIR=$SCALAR_ENLISTMENTDIR/out 12 | -------------------------------------------------------------------------------- /Scripts/Linux/InstallFromSource.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # To test on a Debian-based Linux machine: 4 | # 5 | # ./InstallFromSource.sh 6 | # (outputs Git, GCM, and Scalar versions) 7 | # 8 | # Small clone should work: 9 | # scalar clone https://dev.azure.com/gvfs/ci/_git/ForTests 10 | # 11 | # Big clone for authenticated repo should work: 12 | # scalar clone 13 | # 14 | # Note: if you are using a headless Linux machine, then GCM Core will 15 | # not be able to store your credentials. Instead, create a PAT and 16 | # use that for password (multiple times). This will be fixed soon. 17 | 18 | # Nuke previous data 19 | rm -rf git-vfs*.deb gcmcore-linux*.deb packages*.deb 20 | 21 | echo "Installing dotnet SDK 3.1" 22 | 23 | sudo apt-get update -qq 24 | sudo apt-get install -y apt-transport-https && sudo apt-get update -qq 25 | 26 | sudo apt-get install -y lsb-release wget 27 | LSB_VERSION=$(lsb_release -rs | awk '/^[0-9]+\.[0-9]+$/ { print }') 28 | if [ -z "$LSB_VERSION" ] 29 | then 30 | LSB_VERSION="20.04" 31 | fi 32 | wget https://packages.microsoft.com/config/ubuntu/"$LSB_VERSION"/packages-microsoft-prod.deb -O packages-microsoft-prod.deb 33 | sudo dpkg -i packages-microsoft-prod.deb 34 | 35 | if sudo apt-get install -y dotnet-sdk-3.1 36 | then 37 | echo "dotnet successfully installed" 38 | else 39 | echo "error: failed to install dotnet" 40 | echo "please follow instructions here to install: https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu" 41 | exit 1 42 | fi 43 | 44 | echo "Installing Git from microsoft/git" 45 | wget https://github.com/microsoft/git/releases/download/v2.28.0.vfs.0.0/git-vfs_2.28.0.vfs.0.0.deb 46 | 47 | if ! sudo dpkg -i ./git-vfs_2.28.0.vfs.0.0.deb 48 | then 49 | echo "error: failed to install microsoft/git" 50 | exit 1 51 | fi 52 | 53 | echo "Installing GCM Core" 54 | wget https://github.com/microsoft/Git-Credential-Manager-Core/releases/download/v2.0.246-beta/gcmcore-linux_amd64.2.0.246.34937.deb 55 | 56 | if ! sudo dpkg -i ./gcmcore-linux*.deb 57 | then 58 | echo "error: failed to install GCM core" 59 | exit 1 60 | fi 61 | 62 | echo "Cloning and installing Scalar" 63 | mkdir scalar 64 | git clone --filter=blob:none https://github.com/microsoft/scalar scalar/src 65 | ( 66 | cd scalar/src 67 | dotnet restore 68 | dotnet build --configuration Release --no-restore 69 | dotnet test --configuration Release --no-restore --verbosity normal 70 | sudo rm -rf /usr/local/lib/scalar 71 | sudo mkdir /usr/local/lib/scalar 72 | sudo cp -r ../out/Scalar/bin/Release/netcoreapp3.1/* /usr/local/lib/scalar/ 73 | sudo rm -rf /usr/local/bin/scalar 74 | sudo ln -s /usr/local/lib/scalar/scalar /usr/local/bin/scalar 75 | sudo chmod a+x /usr/local/bin/scalar 76 | ) 77 | 78 | git version 79 | git-credential-manager-core --version 80 | 81 | 82 | if ! scalar version 83 | then 84 | echo "Scalar was not properly installed. Please double-check the build output" 85 | exit 1 86 | fi 87 | -------------------------------------------------------------------------------- /Scripts/Linux/NukeBuildOutputs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh" 4 | 5 | sudo rm -Rf $SCALAR_OUTPUTDIR 6 | 7 | echo git --work-tree=$SCALAR_SRCDIR clean -Xdf -n 8 | git --work-tree=$SCALAR_SRCDIR clean -Xdf -n 9 | -------------------------------------------------------------------------------- /Scripts/Linux/RunFunctionalTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh" 4 | 5 | CONFIGURATION=$1 6 | if [ -z $CONFIGURATION ]; then 7 | CONFIGURATION=Debug 8 | fi 9 | 10 | PUBLISH_FRAGMENT="bin/$CONFIGURATION/netcoreapp3.1/linux-x64/publish" 11 | FUNCTIONAL_TESTS_DIR="$SCALAR_OUTPUTDIR/Scalar.FunctionalTests/$PUBLISH_FRAGMENT" 12 | 13 | # Always test Scalar on the PATH, since it was 14 | # installed with Git itself. 15 | echo "PATH:" 16 | echo "$PATH" 17 | echo "Scalar location:" 18 | where scalar 19 | 20 | TESTS_EXEC="$FUNCTIONAL_TESTS_DIR/Scalar.FunctionalTests" 21 | 22 | mkdir ~/Scalar.FT 23 | 24 | # Consume the first argument 25 | shift 26 | 27 | # Ensure the binary is executable 28 | chmod +x $TESTS_EXEC 29 | 30 | # Run the tests! 31 | $TESTS_EXEC "$@" 32 | -------------------------------------------------------------------------------- /Scripts/Mac/BuildScalarForMac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh" 4 | 5 | CONFIGURATION=$1 6 | if [ -z $CONFIGURATION ]; then 7 | CONFIGURATION=Debug 8 | fi 9 | 10 | VERSION=$2 11 | if [ -z $VERSION ]; then 12 | VERSION="0.2.173.2" 13 | fi 14 | 15 | # If we're building the Profiling(Release) configuration, remove Profiling() for building .NET code 16 | if [ "$CONFIGURATION" == "Profiling(Release)" ]; then 17 | CONFIGURATION=Release 18 | fi 19 | 20 | dotnet publish $SCALAR_SRCDIR/Scalar.sln --runtime osx-x64 -p:ScalarVersion=$VERSION --configuration $CONFIGURATION || exit 1 21 | -------------------------------------------------------------------------------- /Scripts/Mac/CI/CreateFTDrop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . "$(dirname ${BASH_SOURCE[0]})/../InitializeEnvironment.sh" 4 | 5 | CONFIGURATION=$1 6 | SCALAR_STAGEDIR=$2 7 | if [ -z $SCALAR_STAGEDIR ] || [ -z $CONFIGURATION ]; then 8 | echo 'ERROR: Usage: CreateBuildDrop.sh [configuration] [build drop root directory]' 9 | exit 1 10 | fi 11 | 12 | # Set up some paths 13 | SCRIPTS_SRC=$SCALAR_SCRIPTSDIR 14 | TESTS_SRC=$SCALAR_OUTPUTDIR/Scalar.FunctionalTests/bin/$CONFIGURATION/netcoreapp3.1/osx-x64/publish 15 | 16 | SCRIPTS_DEST=$SCALAR_STAGEDIR/src/Scripts/Mac 17 | TESTS_DEST=$SCALAR_STAGEDIR/out/Scalar.FunctionalTests/bin/$CONFIGURATION/netcoreapp3.1/osx-x64/publish 18 | 19 | # Set up the build drop directory structure 20 | rm -rf $SCALAR_STAGEDIR 21 | mkdir -p $SCRIPTS_DEST 22 | mkdir -p $TESTS_DEST 23 | 24 | # Copy to the build drop, retaining directory structure 25 | cp -Rf $SCRIPTS_SRC/ $SCRIPTS_DEST 26 | cp -Rf $TESTS_SRC/ $TESTS_DEST 27 | -------------------------------------------------------------------------------- /Scripts/Mac/CI/CreateInstallerDrop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . "$(dirname ${BASH_SOURCE[0]})/../InitializeEnvironment.sh" 4 | 5 | CONFIGURATION=$1 6 | SCALAR_STAGEDIR=$2 7 | if [ -z $SCALAR_STAGEDIR ] || [ -z $CONFIGURATION ]; then 8 | echo 'ERROR: Usage: CreateInstallerDrop.sh [configuration] [build drop root directory]' 9 | exit 1 10 | fi 11 | 12 | # Set up the installer directory structure 13 | rm -rf $SCALAR_STAGEDIR 14 | mkdir -p $SCALAR_STAGEDIR 15 | 16 | # Copy to the build drop, retaining directory structure 17 | cp -Rf $SCALAR_OUTPUTDIR/Scalar.Installer.Mac/dist/$CONFIGURATION/ $SCALAR_STAGEDIR 18 | -------------------------------------------------------------------------------- /Scripts/Mac/CleanupFunctionalTests.sh: -------------------------------------------------------------------------------- 1 | . "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh" 2 | 3 | pkill -9 -l Scalar.FunctionalTests 4 | pkill -9 -l git 5 | pkill -9 -l scalar 6 | 7 | if [ -d /Scalar.FT ]; then 8 | sudo rm -r /Scalar.FT 9 | fi 10 | -------------------------------------------------------------------------------- /Scripts/Mac/InitializeEnvironment.sh: -------------------------------------------------------------------------------- 1 | SCRIPTDIR="$(dirname ${BASH_SOURCE[0]})" 2 | 3 | # convert to an absolute path because it is required by `dotnet publish` 4 | pushd $SCRIPTDIR &>/dev/null 5 | export SCALAR_SCRIPTSDIR="$(pwd)" 6 | popd &>/dev/null 7 | 8 | export SCALAR_SRCDIR=$SCALAR_SCRIPTSDIR/../.. 9 | 10 | export SCALAR_ENLISTMENTDIR=$SCALAR_SRCDIR/.. 11 | export SCALAR_OUTPUTDIR=$SCALAR_ENLISTMENTDIR/out 12 | -------------------------------------------------------------------------------- /Scripts/Mac/NukeBuildOutputs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh" 4 | 5 | sudo rm -Rf $SCALAR_OUTPUTDIR 6 | 7 | echo git --work-tree=$SCALAR_SRCDIR clean -Xdf -n 8 | git --work-tree=$SCALAR_SRCDIR clean -Xdf -n 9 | -------------------------------------------------------------------------------- /Scripts/Mac/RunFunctionalTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh" 4 | 5 | CONFIGURATION=$1 6 | if [ -z $CONFIGURATION ]; then 7 | CONFIGURATION=Debug 8 | fi 9 | 10 | PUBLISH_FRAGMENT="bin/$CONFIGURATION/netcoreapp3.1/osx-x64/publish" 11 | FUNCTIONAL_TESTS_DIR="$SCALAR_OUTPUTDIR/Scalar.FunctionalTests/$PUBLISH_FRAGMENT" 12 | 13 | # Always test Scalar on the PATH, because it 14 | # was installed with Git 15 | echo "PATH:" 16 | echo "$PATH" 17 | echo "Scalar location:" 18 | where scalar 19 | 20 | TESTS_EXEC="$FUNCTIONAL_TESTS_DIR/Scalar.FunctionalTests" 21 | 22 | mkdir ~/Scalar.FT 23 | 24 | # Consume the first argument 25 | shift 26 | 27 | # Ensure the binary is executable 28 | chmod +x $TESTS_EXEC 29 | 30 | # Run the tests! 31 | $TESTS_EXEC "$@" 32 | -------------------------------------------------------------------------------- /Scripts/Mac/Tracing/Docs/cddl1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/scalar/1f66ccf37864e25fc27c0b786b425eb2695f814e/Scripts/Mac/Tracing/Docs/cddl1.txt -------------------------------------------------------------------------------- /Scripts/Mac/Tracing/Tracing.md: -------------------------------------------------------------------------------- 1 | # Tracing on macOS 2 | 3 | [DTrace](https://en.wikipedia.org/wiki/DTrace) is the main low-level tool for tracing and diagnostics on macOS. 4 | For diagnosing issues potentially caused by Scalar, tracing syscalls to track down errors has proven to be a useful technique on Windows. 5 | On macOS, the DTrace script `dtruss` which ships with the OS can be used for tracing system calls made by processes. 6 | However, it has not been updated for newer OS versions for a while, making some of its features unreliable. 7 | We therefore recommend using our [updated version of the script](./dtruss), and the instructions below assume you are using this version. 8 | 9 | ## TL;DR: This build command is failing/misbehaving and I've been asked to collect a trace 10 | 11 | To capture a trace of the syscalls made by the failing command, run the following: 12 | 13 | ++path-to-vfs4g-source++/Scripts/Mac/Tracing/dtruss -d -e -F -f ++command-to-run++ 2> >(tee ++trace-filename.txt++ >&2) 14 | 15 | ## General tips 16 | 17 | `dtruss` simply outputs a live list of all syscalls (or all calls to one specific syscall) made by the selected/matching process(es), including return value/errno and arguments. 18 | Arguments are formatted for human readability to some extent. (e.g. string arguments are typically printed as a string, not just the raw `char` pointer.) 19 | 20 | The output is written to stderr, so to save it to a file use `2> filename.txt`, or to both save it to file *and* print it to the terminal, this will work in `bash`: 21 | 22 | sudo dtruss -p 1000 2> >(tee clang-trace.txt >&2) 23 | 24 | When starting `dtruss`, wait for the column headers to appear before starting whatever activity you're trying to trace; dtrace can take a few seconds to compile and inject the script, and events that occur before this is done will be dropped. 25 | This is of course not an issue if you are starting the traced command on the `dtruss` command line directly. 26 | The column headers will look something like this, the exact headers will depend on the command line flags passed: 27 | 28 | PID/THRD RELATIVE ELAPSD SYSCALL(args) = return 29 | 30 | ## Specifying processes to trace 31 | 32 | ### Named processes 33 | 34 | Use `-n `, for example if there seems to be a problem with syscalls made by `clang`: 35 | 36 | sudo ./Scripts/Mac/Tracing/dtruss -d -e -n clang 37 | 38 | Note that process names in macOS are limited to `MAXCOMLEN`, i.e. 16 characters. 39 | 40 | The `-d` and `-e` flags enable printing of time stamps (in µs, where 0 = time when `dtruss` was started) and elapsed wall time during the syscall (also µs), respectively. 41 | 42 | ### By PID 43 | 44 | Use `-p ` in place of `-n`. 45 | 46 | ### Tracing a command 47 | 48 | For tracing processes started from the command line, launching the command directly together with dtruss is typically the most convenient. To trace the `ls` command, simply use: 49 | 50 | ./Scripts/Mac/Tracing/dtruss ls 51 | 52 | Note the lack of `sudo` on this command. This was required on stock `dtruss` but meant all commands launched this way ran as the root user. Instead, the script now runs only `dtrace` itself as root via sudo, so you will still need to be an admin (`wheel` group) and type your password. 53 | 54 | ### Tracing child processes 55 | 56 | Often, the thing you're trying to diagnose is a more complex mix of multiple processes; for example, building some software from source will typically kick off many different processes for the build system itself, compilers, linkers, and other tools. For tracing the activity of the whole build, it's often most convenient to trace the root build command *and all processes launched by it, recursively.* 57 | `dtruss` supports this via the `-f` flag, which can be combined with any of the other process selectors. For example: 58 | 59 | ./Scripts/Mac/Tracing/dtruss -d -e -f xcodebuild 60 | 61 | This will run an `xcodebuild` in the current directory, trace that process and any other processes below it in the hierarchy and additionally output time stamps and syscall runtimes. 62 | 63 | ## Changes over stock `dtruss` 64 | 65 | For reference, the following improvements have been made so far: 66 | 67 | * When launching a command with `dtruss`, it's now possible to run the command as a non-root user. Simply run `dtruss [-options] ` as a non-privileged admin user; the command will run as this user, and `dtrace` itself will be run as root via `sudo` (you will likely be prompted for your password). 68 | * Correct logging of `execve()` and `posix_spawn()` syscalls. 69 | * Improvements to output for syscalls with buffer or string parameters. (Better handling of `NULL` pointers, large buffers, etc.) 70 | * A new `-F` option for filtering out common but typically uninteresting syscalls. At the moment, this only filters out `close()` calls returning `EBADF`. Some programs seem to call `close()` on thousands of sequential integers, most of which are not valid file descriptors. 71 | * The mode for following child processes (`-f`) is much more reliable. Previously, processes created with `posix_spawn()` were typically ignored. (They were typically only detected if the parent process had previously already used `fork()`; a mix of both techniques in the same program is rare, however.) `posix_spawn()` is a very common method for creating new processes on macOS nowadays, especially as `fork()`/`execve()` is explicitly disallowed for Objective-C based apps. Following `posix_spawn()`ed processes is now directly supported and some bugs that tended to occur when following *any* child process have also been fixed. 72 | 73 | ### Remaining bugs/limitations in `dtruss` 74 | 75 | * `dtruss` no longer exits when the specified command itself exits. (This regression is a consequence of the non-privileged command launch change.) 76 | * There are still some syscalls which are badly formatted in the trace output. 77 | * The buffers can fill up and miss events when a lot of traced events occur. This is indicated by the message `dtrace: 6228 dynamic variable drops with non-empty dirty list` - try increasing the dynamic variable memory (e.g. `-b 100m` for 100MB - default is currently 30MB) in this case. Better still, try to narrow the focus of your tracing. 78 | * Currently, you can trace either all syscalls or just one, not some subset. 79 | -------------------------------------------------------------------------------- /Scripts/NukeBuildOutputs.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 3 | verify >nul 4 | 5 | powershell -NonInteractive -NoProfile -Command "& { (Get-MpPreference).ExclusionPath | ? {$_.StartsWith('C:\Repos\')} | %%{Remove-MpPreference -ExclusionPath $_} }" 6 | 7 | IF EXIST C:\Repos\ScalarFunctionalTests\enlistment ( 8 | rmdir /s /q C:\Repos\ScalarFunctionalTests\enlistment 9 | ) ELSE ( 10 | ECHO no test enlistment found 11 | ) 12 | 13 | IF EXIST C:\Repos\ScalarPerfTest ( 14 | rmdir /s /q C:\Repos\ScalarPerfTest 15 | ) ELSE ( 16 | ECHO no perf test enlistment found 17 | ) 18 | 19 | IF EXIST %SCALAR_OUTPUTDIR% ( 20 | ECHO deleting build outputs 21 | rmdir /s /q %SCALAR_OUTPUTDIR% 22 | ) ELSE ( 23 | ECHO no build outputs found 24 | ) 25 | 26 | call %SCALAR_SCRIPTSDIR%\StopAllServices.bat 27 | -------------------------------------------------------------------------------- /Scripts/ReinstallScalar.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 3 | 4 | IF "%1"=="" (SET "Configuration=Debug") ELSE (SET "Configuration=%1") 5 | 6 | call %SCALAR_SCRIPTSDIR%\UninstallScalar.bat 7 | 8 | if not exist "c:\Program Files\Git" goto :noGit 9 | for /F "delims=" %%g in ('dir "c:\Program Files\Git\unins*.exe" /B /S /O:-D') do %%g /VERYSILENT /SUPPRESSMSGBOXES /NORESTART & goto :deleteGit 10 | 11 | :deleteGit 12 | rmdir /q/s "c:\Program Files\Git" 13 | 14 | :noGit 15 | REM This is a hacky way to sleep for 2 seconds in a non-interactive window. The timeout command does not work if it can't redirect stdin. 16 | ping 1.1.1.1 -n 1 -w 2000 >NUL 17 | 18 | :runInstallers 19 | call %SCALAR_OUTPUTDIR%\Scalar.Distribution.Windows\dist\%Configuration%\InstallScalar.bat 20 | -------------------------------------------------------------------------------- /Scripts/RestorePackages.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM NO-OP 4 | -------------------------------------------------------------------------------- /Scripts/RunFunctionalTests.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 3 | 4 | IF "%1"=="" (SET "Configuration=Debug") ELSE (SET "Configuration=%1") 5 | 6 | SETLOCAL 7 | SET PATH=C:\Program Files\Scalar;C:\Program Files\Git\cmd;%PATH% 8 | 9 | SET publishFragment=bin\%Configuration%\netcoreapp3.1\win10-x64\publish 10 | SET functionalTestsDir=%SCALAR_OUTPUTDIR%\Scalar.FunctionalTests\%publishFragment% 11 | 12 | ECHO ************************** 13 | ECHO Testing Scalar on the PATH 14 | ECHO ************************** 15 | ECHO PATH: 16 | ECHO %PATH% 17 | ECHO Scalar location: 18 | where scalar 19 | ECHO Git location: 20 | where git 21 | 22 | :startTests 23 | %functionalTestsDir%\Scalar.FunctionalTests /result:TestResultNetCore.xml %2 %3 %4 %5 %6 %7 %8 || goto :endTests 24 | 25 | :endTests 26 | SET error=%errorlevel% 27 | 28 | EXIT /b %error% 29 | -------------------------------------------------------------------------------- /Scripts/StopAllServices.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 3 | 4 | call %SCALAR_SCRIPTSDIR%\StopService.bat Scalar.Service 5 | call %SCALAR_SCRIPTSDIR%\StopService.bat Test.Scalar.Service 6 | -------------------------------------------------------------------------------- /Scripts/StopService.bat: -------------------------------------------------------------------------------- 1 | sc stop %1 2 | verify >nul 3 | -------------------------------------------------------------------------------- /Scripts/UninstallScalar.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 3 | 4 | taskkill /F /T /FI "IMAGENAME eq git.exe" 5 | taskkill /F /T /FI "IMAGENAME eq Scalar.exe" 6 | 7 | if not exist "C:\Program Files\Scalar" goto :end 8 | 9 | call %SCALAR_SCRIPTSDIR%\StopAllServices.bat 10 | 11 | REM Find the latest uninstaller file by date and run it. Goto the next step after a single execution. 12 | for /F "delims=" %%f in ('dir "C:\Program Files\Scalar\unins*.exe" /B /S /O:-D') do %%f /VERYSILENT /SUPPRESSMSGBOXES /NORESTART & goto :deleteScalar 13 | 14 | :deleteScalar 15 | rmdir /q/s "C:\Program Files\Scalar" 16 | 17 | REM Delete ProgramData\Scalar directory (logs, downloaded upgrades, repo-registry, scalar.config). It can affect the behavior of a future Scalar install. 18 | if exist "C:\ProgramData\Scalar" rmdir /q/s "C:\ProgramData\Scalar" 19 | 20 | :end 21 | -------------------------------------------------------------------------------- /Signing.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | Frequently Asked Questions 2 | ========================== 3 | 4 | > **Note: Scalar has moved!** It is now part of [the `microsoft/git` fork][microsoft-git] 5 | > and thus this document has [a new version in that repository][git-faq]. 6 | 7 | [microsoft-git]: https://github.com/microsoft/git 8 | [git-faq]: https://github.com/microsoft/git/blob/HEAD/contrib/scalar/docs/faq.md 9 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | > **Note: Scalar has moved!** It is now part of [the `microsoft/git` fork][microsoft-git] 5 | > and thus this document has [a new version in that repository][git-getting-started]. 6 | 7 | [microsoft-git]: https://github.com/microsoft/git 8 | [git-getting-started]: https://github.com/microsoft/git/blob/HEAD/contrib/scalar/docs/getting-started.md 9 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | Scalar: Enabling Git at Scale 2 | ============================= 3 | 4 | > **Note: Scalar has moved!** It is now part of [the `microsoft/git` fork][microsoft-git] 5 | > and thus this document has [a new version in that repository][git-index]. 6 | -------------------------------------------------------------------------------- /docs/philosophy.md: -------------------------------------------------------------------------------- 1 | The Philosophy of Scalar 2 | ======================== 3 | 4 | > **Note: Scalar has moved!** It is now part of [the `microsoft/git` fork][microsoft-git] 5 | > and thus this document has [a new version in that repository][git-philosophy]. 6 | 7 | [microsoft-git]: https://github.com/microsoft/git 8 | [git-philosophy]: https://github.com/microsoft/git/blob/HEAD/contrib/scalar/docs/philosophy.md 9 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | > **Note: Scalar has moved!** It is now part of [the `microsoft/git` fork][microsoft-git] 5 | > and thus this document has [a new version in that repository][git-troubleshooting]. 6 | 7 | [microsoft-git]: https://github.com/microsoft/git 8 | [git-troubleshooting]: https://github.com/microsoft/git/blob/HEAD/contrib/scalar/docs/troubleshooting.md 9 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild-sdks": { 3 | "Microsoft.Build.Traversal": "2.0.19", 4 | "Microsoft.Build.NoTargets": "1.0.85" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------