From 11237ed2a139404cf23a7d1a2e0df972e42b1caf Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 15 Oct 2026 23:47:09 +0200 Subject: [PATCH 1/3] fix: replace "/" in git references when naming install folders installGithubRepository renamed an unzipped archive to "-", and lookForRepository searched the path for the same name. A reference such as "codex/nansen-compatibility-contract" made that name a nested path: movefile created "StructEditor-codex/" and put the repository in "nansen-compatibility-contract/" inside it. The repository folder was then named after the end of the reference, and a later lookup for a branch named "codex" matched the parent folder and failed its README and LICENSE check. Both functions now take the name from repositoryFolderName, which replaces each "/" in the reference with "-". GitHub names the top-level folder of a branch archive the same way (observed for StructEditor's codex/nansen-compatibility-contract and MatBox's feature/buildtool-support), so a branch install needs no rename. The references "release/1.0" and "release-1.0" map to the same folder name. Co-Authored-By: Claude Opus 5.5 --- .../+internal/+github/repositoryFolderName.m | 19 ++++++++++ .../+internal/+pathtool/lookForRepository.m | 11 ++++-- .../+internal/installGithubRepository.m | 11 +++--- .../+unittest/GithubReferenceTest.m | 37 +++++++++++++++++-- 4 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 code/+matbox/+setup/+internal/+github/repositoryFolderName.m 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 7601e42..c7243c7 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,6 +16,15 @@ % exercise the rename to "-" done by installGithubRepository. TagName = "v0.1.0" CommitSha = "4e8eaa6" + % 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) @@ -49,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 testLookForRepositoryFindsTagFolder(testCase) folderPath = testCase.createRepositoryFolder( ... testCase.RepositoryName + "-" + testCase.TagName); @@ -75,6 +99,10 @@ function testInstallTagPinnedRequirement(testCase) function testInstallCommitPinnedRequirement(testCase) testCase.verifyInstallAndReinstall(testCase.CommitSha) end + + function testInstallHierarchicalBranchPinnedRequirement(testCase) + testCase.verifyInstallAndReinstall(testCase.ExistingHierarchicalBranchName) + end end methods (Access = private) @@ -90,7 +118,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); From 9ff77d82fe69267034d7b1e9a38dae4085666ec7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Oct 2026 21:53:06 +0000 Subject: [PATCH 2/3] Update GitHub badges [skip ci] --- .github/badges/tests.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg index 828f2d7..f9defd9 100644 --- a/.github/badges/tests.svg +++ b/.github/badges/tests.svg @@ -1 +1 @@ -tests: 83 passedtests83 passed +tests: 86 passedtests86 passed From 156b8f890c71c0a41d7cf4566f9000bc9e0cdfd7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 05:23:08 +0000 Subject: [PATCH 3/3] Update GitHub badges [skip ci] --- .github/badges/tests.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg index f9defd9..4b080fa 100644 --- a/.github/badges/tests.svg +++ b/.github/badges/tests.svg @@ -1 +1 @@ -tests: 86 passedtests86 passed +tests: 91 passedtests91 passed