diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg index 8306689..4b080fa 100644 --- a/.github/badges/tests.svg +++ b/.github/badges/tests.svg @@ -1 +1 @@ -tests: 88 passedtests88 passed +tests: 91 passedtests91 passed diff --git a/code/+matbox/+setup/+internal/+github/repositoryFolderName.m b/code/+matbox/+setup/+internal/+github/repositoryFolderName.m new file mode 100644 index 0000000..0fb2c41 --- /dev/null +++ b/code/+matbox/+setup/+internal/+github/repositoryFolderName.m @@ -0,0 +1,19 @@ +function folderName = repositoryFolderName(repositoryName, gitRef) +% repositoryFolderName - Name of the folder holding a repository at a git reference +% +% folderName = repositoryFolderName(repositoryName, gitRef) returns +% "-" with each "/" in gitRef replaced by "-". +% A git reference may contain "/" (as in "release/1.0"), which would +% otherwise make the name a nested path. As observed on github.com, a +% branch archive unpacks to a folder named by the same rule. +% +% Different references can map to the same name, for example +% "release/1.0" and "release-1.0". + + arguments + repositoryName (1,1) string + gitRef (1,1) string + end + + folderName = repositoryName + "-" + replace(gitRef, "/", "-"); +end diff --git a/code/+matbox/+setup/+internal/+pathtool/lookForRepository.m b/code/+matbox/+setup/+internal/+pathtool/lookForRepository.m index 3c8fd0e..8042aca 100644 --- a/code/+matbox/+setup/+internal/+pathtool/lookForRepository.m +++ b/code/+matbox/+setup/+internal/+pathtool/lookForRepository.m @@ -12,14 +12,17 @@ pathList = strsplit(path, pathsep); % Candidate folder names, most specific first: - % {repositoryName}-{gitRef} is the folder name of a repository that - % was downloaded as a zip archive (see installGithubRepository). + % repositoryFolderName(repositoryName, gitRef) is the folder name of a + % repository that was downloaded as a zip archive (see + % installGithubRepository). % {repositoryName} is the folder name of a repository cloned with git. % Each candidate is matched both as the last folder of a path entry % ("$") and as a parent folder of a path entry (filesep). + archiveFolderName = ... + matbox.setup.internal.github.repositoryFolderName(repositoryName, gitRef); candidateFolderNames = [ ... - sprintf("%s-%s", repositoryName, gitRef), ... - sprintf("%s-%s", repositoryName, gitRef), ... + archiveFolderName, ... + archiveFolderName, ... string(repositoryName), ... string(repositoryName)]; candidateSuffixes = ["$", filesep, "$", filesep]; diff --git a/code/+matbox/+setup/+internal/installGithubRepository.m b/code/+matbox/+setup/+internal/installGithubRepository.m index 1d03522..9cc64d5 100644 --- a/code/+matbox/+setup/+internal/installGithubRepository.m +++ b/code/+matbox/+setup/+internal/installGithubRepository.m @@ -131,14 +131,15 @@ % scheme; as observed on github.com, a leading "v" is dropped from tag % names (v1.0.0 unpacks to -1.0.0) and an abbreviated commit SHA is % expanded to the full hash, while branch archives unpack to -% -. Renaming to the reference exactly as written in the -% requirement makes the folder name predictable regardless of how GitHub -% names the archive, so that lookForRepository recognises the -% installation on later runs instead of downloading again. +% - with each "/" replaced by "-". Renaming to the name +% given by repositoryFolderName, which is derived from the reference as +% written in the requirement, makes the folder name predictable +% regardless of how GitHub names the archive, so that lookForRepository +% recognises the installation on later runs instead of downloading again. [parentFolder, folderName, folderNameSuffix] = fileparts(repoFolder); actualName = strcat(folderName, folderNameSuffix); % fileparts splits names like "repo-1.0.0" at the last dot - expectedName = sprintf('%s-%s', repositoryName, gitRef); + expectedName = matbox.setup.internal.github.repositoryFolderName(repositoryName, gitRef); % Compare case-insensitively: the archive uses the repository's canonical % casing, which may differ from the casing in the requirement URL. diff --git a/tools/tests/+matboxtools/+unittest/GithubReferenceTest.m b/tools/tests/+matboxtools/+unittest/GithubReferenceTest.m index 69a1a5c..791ddad 100644 --- a/tools/tests/+matboxtools/+unittest/GithubReferenceTest.m +++ b/tools/tests/+matboxtools/+unittest/GithubReferenceTest.m @@ -3,8 +3,9 @@ % % A GitHub requirement may pin a branch name, tag name, or commit SHA % using the "@" form. These tests cover parsing the reference, -% locating an installed copy on the search path, and installing from -% GitHub. The install tests require network access. +% naming the installation folder, locating an installed copy on the +% search path, and installing from GitHub. The install tests require +% network access. properties (Constant) RepositoryUrl = "https://github.com/ehennestad/StructEditor" @@ -15,9 +16,15 @@ % exercise the rename to "-" done by installGithubRepository. TagName = "v0.1.0" CommitSha = "4e8eaa6" - % A branch name in the hierarchical form, used only by parsing tests, - % so it need not exist in the test repository. + % A branch name in the hierarchical form and the folder name it + % maps to. Used only by tests that need no network access, so the + % branch need not exist in the test repository. HierarchicalBranchName = "release/1.0" + HierarchicalBranchFolderName = "StructEditor-release-1.0" + % A branch in the test repository whose name contains "/". As + % observed on github.com, its archive folder is named + % "StructEditor-codex-nansen-compatibility-contract". + ExistingHierarchicalBranchName = "codex/nansen-compatibility-contract" end methods (TestMethodSetup) @@ -52,6 +59,20 @@ function testParseUrlWithCommitReference(testCase) testCase.verifyEqual(gitRef, testCase.CommitSha) end + function testRepositoryFolderNameReplacesSlashes(testCase) + folderName = matbox.setup.internal.github.repositoryFolderName( ... + testCase.RepositoryName, testCase.HierarchicalBranchName); + testCase.verifyEqual(folderName, testCase.HierarchicalBranchFolderName) + end + + function testLookForRepositoryFindsHierarchicalBranchFolder(testCase) + folderPath = testCase.createRepositoryFolder(testCase.HierarchicalBranchFolderName); + [exists, repositoryPath] = matbox.setup.internal.pathtool.lookForRepository( ... + testCase.RepositoryName, testCase.HierarchicalBranchName); + testCase.verifyTrue(exists) + testCase.verifyEqual(repositoryPath, folderPath) + end + function testParseUrlWithSlashInReference(testCase) [~, repositoryName, gitRef] = matbox.setup.internal.github.parseRepositoryURL( ... testCase.RepositoryUrl + "@" + testCase.HierarchicalBranchName); @@ -113,6 +134,10 @@ function testInstallTagPinnedRequirement(testCase) function testInstallCommitPinnedRequirement(testCase) testCase.verifyInstallAndReinstall(testCase.CommitSha) end + + function testInstallHierarchicalBranchPinnedRequirement(testCase) + testCase.verifyInstallAndReinstall(testCase.ExistingHierarchicalBranchName) + end end methods (Access = private) @@ -128,7 +153,10 @@ function testInstallCommitPinnedRequirement(testCase) function verifyInstallAndReinstall(testCase, gitRef) sourceUri = testCase.RepositoryUrl + "@" + gitRef; - expectedFolder = string(fullfile(pwd, testCase.RepositoryName + "-" + gitRef)); + % The installation folder sits directly in the installation + % location, with each "/" of the reference replaced by "-". + expectedFolder = string(fullfile(pwd, ... + testCase.RepositoryName + "-" + replace(gitRef, "/", "-"))); installResult = matbox.setup.installFromSourceUri(sourceUri, ... "InstallationLocation", pwd, "Verbose", false);