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 @@
-
+
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);