Git dependency subfolders via ?path= - #2497
Merged
Merged
Conversation
Allow a Git dependency URL to select a project inside the repository, for example https://host/repo.git?path=/libraryA. Dependencies at the same commit share one BeefManaged/<hash> clone; each selected project runs its own Setup and records its result in a version 2 BeefManaged.toml with a [[Projects]] entry per path. A top-level Setup key mirrors the root entry so older Beef builds keep reading root-only libraries. Version 1 manifests remain readable and migrate when another project in the clone is initialized. Workspace locks store the full URL. Lock and load-time failures are reported through the caller instead of failing inside the project loop, missing folders record nothing so the next load repeats the same error, and caches with unsupported metadata are rebuilt with the reason printed. URLDecode now rejects malformed escapes and returns a Result.
Collaborator
|
At this point I think you've spent more time thinking about and implementing Git dependencies than I have, and these all appear to be reasonable changes. I'll accept this as-is and we'll see if anyone has any input or issues. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I've been working on issue #2453 on and off since i made the issue, but finally decided to get it polished up this weekend.
Since I didn't get any specific feedback the preferred approach, I've kept with the overall design i outlined in the issue.
This pull request introduces a Version 2 BeefManaged.toml file with a very similar format to the original but with the addition of a
[[Projects]]array added. Which allows multiple projects within a given cloned repo to be tracked for a beef project.This implementation will only clone any given repo once for a given commit hash, if 2 dependencies in the same repo resolve to different commit hashes there will still be 2 copies of the repo.
Setup remains project-local. Previously, the project root was always the repository root. With this change, setup runs from the selected project’s Setup/ directory. Existing root-level dependencies retain their behavior; selecting a subfolder does not automatically run the repository-root setup.
This is the new behavior:
Previously, Beef ran one Setup/ per managed Git dependency, located at the repository root:
Here is the toml produced for the case above where the root project, libraryA, and libraryB are all initialized:
The top-level Setup key mirrors the Path = "." entry for compatibility: any Beef build prior to this PR reads only that key and never checks FileVersion. This allows root-only libraries to continue loading when a managed cache is shared between such a build and one with this change.
If no workspaces have initialized the root level project in this cached clone no top-level Setup key will exist:
Existing version 1 caches remain readable and are not rewritten by a root-only dependency. The first time a subfolder is initialized for that clone, the root's Setup key is copied to a Path = "." entry and the file is rewritten as version 2.
It's worth explicitly noting here that Setup = true on an entry means the project initialized successfully (whether or not it had a Setup folder), false means its setup attempted to run but did not complete successfully, and a missing entry means it has not been initialized yet.
The syntax for selecting projects is based on a url query parameter that is parsed by beef, as mentioned in the issue.
https://github.com/user/proj.git?path=/libraryAhttps://github.com/user/proj.git?path=/libraryBThis design is based on what unity does for it's git based package manager which i've found over the years works very well in my projects.
The workspace lock file will store the full dependency URL including
?path=, so two projects from one repositoryare locked independently:
Other changes in this PR as found by claude:
#or starting with?is rejected withInvalid git project path. Only thepathquery parameter is consumed; anything else in the query string is passed through to Git unchanged.?path=on a dependency re-resolves the version and writes a new lock.BeefProj.tomlis missing records nothing in the manifest, so every load reports that specific error instead of a cached "previously failed setup". A setup that exits successfully but leaves noBeefProj.tomlis recorded as failed.GetWithVersionreturning success without doing anything when a lock existed with a different URL. It now re-resolves the dependency.BeefManaged.tomlcan't be parsed or has aFileVersionother than 1 or 2 is deleted and cloned again. The reason is printed at normal verbosity, e.g.Unsupported managed metadata (FileVersion 3) at '<path>', rebuilding.-cleancacheand Clear Managed Cache clean the whole commit's clone, which affects every project sharing it. Each hash is rebuilt once per run even when several projects select it.IDEUtils.URLDecodenow returns aResultand rejects incomplete or non-hex escapes (%2,%GG,%+1). Previously a bad escape decoded to a NUL byte. The two clipboard file-URI callers in the project panel skip malformed lines; valid encoded paths are unaffected.Something else i wanted to bring up is for path case handling (which i also left a comment about in the code):
The manifest entry paths are matched with
Environment.IsFileSystemCaseSensitive, this same approach is also used elsewhere in the IDE and corelib. This is not entirely a correct implementation in all edge cases because case sensitivity generally is a property of the file system rather than the OS. PLUS Windows 10+ can change case sensitivity per directory (https://learn.microsoft.com/en-us/windows/wsl/case-sensitivity), so a global flag is an approximation. It is usually fine but longer-term there is room for improvements across the code to handle this more correctly. This PR makes no attempt to improve these edge cases, but i did want to at least note that this is a potential issue more generally as it was something i noticed going through the code.And finally one thing to note about compatibility with versions prior to this change:
Workspaces that use ?path= require a build with this change. Verified against a build from current master: root-only workspaces load in both directions without re-running setup thanks to the mirrored top-level Setup key. A pre-PR build given a ?path= workspace either fails to resolve the dependency (cold cache), reports "previous failed setup" (subfolder-only cache), or, if the root is also a dependency and the cache is warm, silently loads the repository root in place of the selected subfolder. During testing running prior builds without -cleancache left manifest and lock files contents unchanged, so switching back to a newer build recovers without intervention.
So overall this is quite a large rework of how the PackMan class works generally due to the additional complexity of this feature. But i do think the end result is worth having.
For transparency this work was llm assisted, i have heavily verified through testing and review of the code(and through the use of multiple models for finding edge cases and issues) prior to commit.