Skip to content

Commit 474b30e

Browse files
mikolalysenkoclaude
andcommitted
Merge main into agent/v5-nuget-gpf-shadow
Resolve the in-sync re-run branch against #1330: keep the stale global packages folder warning (#352) and main's uuid .gitignore backfill. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2 parents aa0a446 + eca5d24 commit 474b30e

14 files changed

Lines changed: 403 additions & 21 deletions

File tree

‎crates/socket-patch-cli/CLI_CONTRACT.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -815,8 +815,8 @@ to **six flavors**.
815815
| pypi / pdm (pdm.lock) | (rebuilt wheel) | lock-only: the `[[package]]` gains the local-file `path` + `files[]` hash. pyproject + `content_hash` untouched. Non-fixture `[metadata] strategy` / hash-less locks refused | `pdm sync` (+ `pdm install --check`), cold cache |
816816
| pypi / pipenv (Pipfile.lock) | (rebuilt wheel) | lock-only: the `default`/`develop` entry → `{file, hashes:[sha256-of-our-wheel]}`. Pipfile + `_meta.hash` untouched. Emits `vendor_integrity_unverified` — pipenv does not hash-check file entries; the committed wheel bytes are the protection | `pipenv install --deploy` (+ `pipenv verify`), cold cache |
817817
| pypi / requirements.txt (pip / `uv pip`) | (rebuilt wheel) | pin line → `./<wheel>` (markers carried over; transitive deps appended), plus `--hash=sha256:<hex>` only when the requirements tree is already in pip's hash-checking mode (any `--hash` or `--require-hashes`) | `pip install -r` / `uv pip install -r` **run from the project root** (both resolve bare paths against the CWD) |
818-
| nuget | deterministically rebuilt `.nupkg` at `<idLower>.<versionNorm>.nupkg` (the uuid dir IS a NuGet folder feed; the stale embedded signature is dropped — unsigned is accepted under NuGet's default validation) | `nuget.config` source + `packageSourceMapping` for the id (creating the mapping from scratch ALSO fans a `<package pattern="*" />` out to every pre-existing source — mapping is exclusive, NU1100 otherwise) **+** `packages.lock.json` `contentHash` → `base64(sha512(nupkg))` for the entries at the patched version in every lock a project under the root restores into — the root `packages.lock.json`, member projects' locks, `packages.<Project>.lock.json`, a literal `NuGetLockFilePath` (v5.0 #353/#514; an unevaluable `NuGetLockFilePath` is refused with `vendor_nuget_lock_path_unresolved`) — (`vendor_nuget_no_lockfile` warning when there is none; a lock that also resolves the id at another version is refused with `vendor_nuget_lock_other_version`, nothing written) | `dotnet restore --locked-mode`, cold cache, `--network none` (tampered nupkg fails NU1403) |
819-
| maven | the patched `.jar` + the upstream pom (only its `<version>` suffixed; transitives survive) + `.sha1` sidecars + an ownership marker under `.socket/vendor/maven2/<g-path>/<a>/<v>-socket.<hex8>/` | every pom root, single-module (a reactor of one) or multi-module: the pinned `<version>` + `<dependencyManagement>` pin, `.mvn/maven.config` (`maven.repo.local.tail`) and the `socket-patch-vendor` fallback file repository (`checksumPolicy=fail`); Gradle, sbt and scala-cli roots go to the same JVM backend (ledger ecosystem `jvm`). Pre-v5 `maven_pom_repository` entries (`<repository>` to `.socket/vendor/maven/<uuid>`) are revert-only: vendoring their root is refused (`vendor_jvm_shape_unsupported`, `legacy_maven_root`) | `mvn` build on a fresh checkout with a warm local repository and behind `mirrorOf external:*` (host capstone `e2e_vendor_maven_build` across the Maven matrix) |
818+
| nuget | deterministically rebuilt `.nupkg` at `<idLower>.<versionNorm>.nupkg` (the uuid dir IS a NuGet folder feed; the stale embedded signature is dropped — unsigned is accepted under NuGet's default validation), plus `<uuid>/.gitignore` (`!*`: re-includes the nupkg against the project's ignores, such as VisualStudio.gitignore's `*.nupkg`); refuses `vendor_artifact_gitignored` when git would still drop it | `nuget.config` source + `packageSourceMapping` for the id (creating the mapping from scratch ALSO fans a `<package pattern="*" />` out to every pre-existing source — mapping is exclusive, NU1100 otherwise) **+** `packages.lock.json` `contentHash` → `base64(sha512(nupkg))` for the entries at the patched version in every lock a project under the root restores into — the root `packages.lock.json`, member projects' locks, `packages.<Project>.lock.json`, a literal `NuGetLockFilePath` (v5.0 #353/#514; an unevaluable `NuGetLockFilePath` is refused with `vendor_nuget_lock_path_unresolved`) — (`vendor_nuget_no_lockfile` warning when there is none; a lock that also resolves the id at another version is refused with `vendor_nuget_lock_other_version`, nothing written) | `dotnet restore --locked-mode`, cold cache, `--network none` (tampered nupkg fails NU1403) |
819+
| maven | the patched `.jar` + the upstream pom (only its `<version>` suffixed; transitives survive) + `.sha1` sidecars + an ownership marker under `.socket/vendor/maven2/<g-path>/<a>/<v>-socket.<hex8>/`; every JVM tree root (`.socket/vendor/maven2`, Gradle's `.socket/vendor/gradle`, Coursier's `.socket/vendor/coursier`) owns a `.gitignore` (`!*`) that re-includes the jars against the project's ignores, such as Java.gitignore's `*.jar`, plus a `.gitattributes` (`-text`); a tree root git ignores itself refuses `vendor_artifact_gitignored` | every pom root, single-module (a reactor of one) or multi-module: the pinned `<version>` + `<dependencyManagement>` pin, `.mvn/maven.config` (`maven.repo.local.tail`) and the `socket-patch-vendor` fallback file repository (`checksumPolicy=fail`); Gradle, sbt and scala-cli roots go to the same JVM backend (ledger ecosystem `jvm`). Pre-v5 `maven_pom_repository` entries (`<repository>` to `.socket/vendor/maven/<uuid>`) are revert-only: vendoring their root is refused (`vendor_jvm_shape_unsupported`, `legacy_maven_root`) | `mvn` build on a fresh checkout with a warm local repository and behind `mirrorOf external:*` (host capstone `e2e_vendor_maven_build` across the Maven matrix) |
820820

821821
Ecosystems with no vendor backend (jsr) refuse per-purl with
822822
`vendor_unsupported_ecosystem`. yarn-berry **PnP**
@@ -1420,8 +1420,8 @@ Every `--json` invocation emits a single JSON object that follows the **unified
14201420
| `vendor_dep_manifest_unlocked` | refused | vendor (yarn classic): the patch rewrites the package's own `package.json` to depend on a descriptor (`name@range`) no `yarn.lock` block is keyed by — an added dependency, or an existing one moved to a new range. yarn 1 builds its install graph from the lock, so the rewired block would name a dependency it never resolves: online frozen installs fetch it unpinned, `--offline` installs fail and every plain `yarn install` re-saves the lock (#591). Refused after staging and before any wiring is written (the staged uuid dir is removed); the detail names the descriptors. Remedy: lock them first (for example `yarn add <descriptor>`), then re-run. A dry run, which stages nothing, does not foresee it. |
14211421
| `redirect_yarn_classic_dep_manifest_unlocked` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (yarn classic): the served tarball's own `package.json` depends on a descriptor no `yarn.lock` block is keyed by. yarn 1 installs only what the lock names, so a pin would install the patched package without that dependency (#591). The dep is not pinned and never confirmed; the lock is left as it was. Same remedy as `vendor_dep_manifest_unlocked`. |
14221422
| `redirect_yarn_classic_dep_manifest_rewritten` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (yarn classic): the served tarball's own `package.json` declares other dependencies than the pinned block's sub-maps, every descriptor already locked; the block's `dependencies:` / `optionalDependencies:` sub-maps are rewritten to match (#591). |
1423-
| `vendor_artifact_gitignored` | `failed` | vendor (vlt and the npm-family tarball flavors: npm, pnpm, bun, yarn classic, yarn berry): inside a git work tree, `git check-ignore --no-index` reports the new artifact's uuid directory as ignored by a rule its own `.gitignore` cannot override (such as a root `.socket/` or `vendor/` rule; the detail names the rule). Remedy: drop that rule for `.socket/vendor/`. Refused before any write. A file rule such as `*.tgz` is overridden by the `<uuid>/.gitignore` vendoring writes; if the written artifact still reads as ignored, the run refuses and removes the uuid dir it created. |
1424-
| `vendor_artifact_gitignore_unchecked` | warning | vendor (vlt and the npm-family tarball flavors): git is installed but could not answer the ignore check for the written vendored directory (it failed to start, ran past 30 s, or `rev-parse` / `check-ignore` exited with an error); the package is vendored and the detail names what failed. Remedy: make sure no ignore rule covers `.socket/` before committing. Git absent, or a project outside any work tree, raises nothing. |
1423+
| `vendor_artifact_gitignored` | `failed` | vendor (vlt, the npm-family tarball flavors — npm, pnpm, bun, yarn classic, yarn berry — NuGet, and the JVM trees of Maven, Gradle, sbt and scala-cli): inside a git work tree, `git check-ignore --no-index` reports the new artifact's uuid directory (JVM: its tree root, such as `.socket/vendor/maven2`) as ignored by a rule its own `.gitignore` cannot override (such as a root `.socket/` or `vendor/` rule; the detail names the rule). Remedy: drop that rule for `.socket/vendor/`. Refused before any write, dry run included. A file rule such as `*.tgz`, `*.nupkg` or `*.jar` is overridden by the `!*` `.gitignore` vendoring writes into the uuid dir or tree root; if a written tarball, directory payload or nupkg still reads as ignored, the run refuses and removes the uuid dir it created. |
1424+
| `vendor_artifact_gitignore_unchecked` | warning | vendor (vlt, the npm-family tarball flavors and NuGet): git is installed but could not answer the ignore check for the written vendored directory (it failed to start, ran past 30 s, or `rev-parse` / `check-ignore` exited with an error); the package is vendored and the detail names what failed. Remedy: make sure no ignore rule covers `.socket/` before committing. Git absent, or a project outside any work tree, raises nothing. |
14251425
| `vendor_ledger_entry_missing` | `failed` | vendor (vlt): the only installed copy is vlt's link to a committed vendored directory, but the vendor ledger has no entry for the package; restore `.socket/vendor/state.json` from version control (v5.0: `repair` no longer re-synthesizes it). Replaces the `package_not_installed` skip. |
14261426
| `vendor_variant_ambiguous` | `failed` | vendor / scan / get `--mode vendored` (pypi, gem): the package is not installed and the manifest holds several release variants of it (`?artifact_id=` / `?platform=`), none of which the vendor ledger records, so nothing says which distribution to vendor; install the package or keep one release variant. A variant the ledger records (at any patch uuid) is taken as the wired one and its siblings are left out without an event. |
14271427
| `vendor_artifact_missing` | reason | The recorded artifact is missing; repair requires an online exact redownload. |

‎crates/socket-patch-cli/tests/e2e_vendor_jvm_build.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ fn maven_reactor_vendor_fresh_checkout_offline_build_and_byte_exact_revert() {
425425
let mut want_added = vec![
426426
".mvn/maven.config".to_string(),
427427
".socket/vendor/maven2/.gitattributes".to_string(),
428+
".socket/vendor/maven2/.gitignore".to_string(),
428429
format!("{tree}/{ARTIFACT}-{SV}.jar"),
429430
format!("{tree}/{ARTIFACT}-{SV}.jar.sha1"),
430431
format!("{tree}/{ARTIFACT}-{SV}.pom"),
@@ -756,6 +757,7 @@ fn gradle_multi_project_vendor_locked_offline_tamper_and_byte_exact_revert() {
756757
socket_patch_core::vendor::jvm::gradle::SCRIPT_REL.to_string(),
757758
socket_patch_core::vendor::jvm::gradle::INDEX_REL.to_string(),
758759
".socket/vendor/gradle/.gitattributes".to_string(),
760+
".socket/vendor/gradle/.gitignore".to_string(),
759761
".socket/gradle/.gitattributes".to_string(),
760762
".socket/vendor/.gitattributes".to_string(),
761763
socket_patch_core::vendor::jvm::gradle::derived_metadata_rel(GROUP, ARTIFACT),

‎crates/socket-patch-cli/tests/vendor_jvm_cli.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,7 @@ fn gradle_vendor_429_crlf_checkout_checks_and_reverts_clean() {
949949
"proj/.socket/vendor/.gitattributes",
950950
"proj/.socket/vendor/gradle-index.tsv",
951951
"proj/.socket/vendor/gradle/.gitattributes",
952+
"proj/.socket/vendor/gradle/.gitignore",
952953
FOO_METADATA,
953954
];
954955
for rel in text_files {

‎crates/socket-patch-core/src/vendor/jvm/apply.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ fn is_owned_file(rel: &str) -> bool {
9595
[
9696
maven_reactor::GITATTRIBUTES_REL,
9797
gradle::GITATTRIBUTES_REL,
98+
gradle::GITIGNORE_REL,
9899
gradle::SCRIPT_GITATTRIBUTES_REL,
99100
gradle::VENDOR_GITATTRIBUTES_REL,
100101
gradle::SCRIPT_REL,

‎crates/socket-patch-core/src/vendor/jvm/gradle.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub const SCRIPT_REL: &str = ".socket/gradle/socket-patch.settings.gradle";
4646
use super::layout::GRADLE_TREE as TREE_ROOT;
4747
/// The tree root's `.gitattributes`, shared by every Gradle patch.
4848
pub const GITATTRIBUTES_REL: &str = ".socket/vendor/gradle/.gitattributes";
49+
/// The tree root's `.gitignore` (`!*`): re-includes the vendored jars
50+
/// against a user's `*.jar` rule (Java.gitignore), #620 / #1061.
51+
pub const GITIGNORE_REL: &str = ".socket/vendor/gradle/.gitignore";
4952
/// `.socket/gradle/`'s `.gitattributes` (`* -text`): the settings scripts
5053
/// there stay byte-exact on a `core.autocrlf` checkout (#429). Shared with
5154
/// the hosted script.
@@ -511,6 +514,12 @@ pub fn plan(
511514
records.push(created_or_adopted(SCRIPT_REL, read(SCRIPT_REL).is_some()));
512515
writes.push(text_write(SCRIPT_REL, SCRIPT.as_bytes().to_vec()));
513516
records.push(owned_file(read, GITATTRIBUTES_REL, &mut writes));
517+
records.push(super::owned_file_with(
518+
read,
519+
GITIGNORE_REL,
520+
super::coursier_tree::GITIGNORE.as_bytes(),
521+
&mut writes,
522+
));
514523
records.push(owned_file(read, SCRIPT_GITATTRIBUTES_REL, &mut writes));
515524
records.push(vendor_gitattributes(read, &mut writes));
516525

@@ -1071,6 +1080,7 @@ pub fn unplan(read: ReadFn<'_>, c: &Coords<'_>, records: &[WiringRecord]) -> Jvm
10711080
for (rel, expected) in [
10721081
(SCRIPT_REL, SCRIPT),
10731082
(GITATTRIBUTES_REL, super::TREE_GITATTRIBUTES),
1083+
(GITIGNORE_REL, super::coursier_tree::GITIGNORE),
10741084
(SCRIPT_GITATTRIBUTES_REL, super::TREE_GITATTRIBUTES),
10751085
] {
10761086
if rel == SCRIPT_GITATTRIBUTES_REL && hosted_left {
@@ -2585,6 +2595,7 @@ mod tests {
25852595
(".socket/vendor/.gitattributes", false),
25862596
(".socket/vendor/gradle-index.tsv", false),
25872597
(".socket/vendor/gradle/.gitattributes", false),
2598+
(".socket/vendor/gradle/.gitignore", false),
25882599
(".socket/vendor/gradle/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar", true),
25892600
(".socket/vendor/gradle/com/google/code/gson/gson/2.10.1/gson-2.10.1.pom", true),
25902601
(".socket/vendor/gradle/com/google/code/gson/gson/2.10.1/socket-patch.vendor.json", true),
@@ -2614,6 +2625,7 @@ mod tests {
26142625
text_of(&plan, ".socket/vendor/gradle/.gitattributes"),
26152626
"* -text\n"
26162627
);
2628+
assert_eq!(text_of(&plan, GITIGNORE_REL), "!*\n");
26172629
assert!(plan.warnings.is_empty(), "{:?}", plan.warnings);
26182630
}
26192631

‎crates/socket-patch-core/src/vendor/jvm/layout.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ pub const CAPTURED_FILES: &[&str] = &[
163163
super::gradle::SCRIPT_REL,
164164
super::maven_reactor::GITATTRIBUTES_REL,
165165
super::gradle::GITATTRIBUTES_REL,
166+
super::gradle::GITIGNORE_REL,
166167
super::gradle::SCRIPT_GITATTRIBUTES_REL,
167168
super::gradle::VENDOR_GITATTRIBUTES_REL,
168169
super::coursier_tree::INDEX_REL,
@@ -543,6 +544,7 @@ mod tests {
543544
under(maven_reactor::GITATTRIBUTES_REL, MAVEN2_TREE);
544545
under(sbt::TREE_GITIGNORE_REL, MAVEN2_TREE);
545546
under(gradle::GITATTRIBUTES_REL, GRADLE_TREE);
547+
under(gradle::GITIGNORE_REL, GRADLE_TREE);
546548
under(coursier_tree::GITIGNORE_REL, COURSIER_TREE);
547549
under(coursier_tree::GITATTRIBUTES_REL, COURSIER_TREE);
548550
under(scala_cli::GUARD_REL, COURSIER_TREE);

‎crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ use super::layout::MAVEN2_TREE as TREE_ROOT;
2525
pub const MAVEN_CONFIG: &str = ".mvn/maven.config";
2626
/// The tree root's `.gitattributes`, shared by every Maven patch.
2727
pub const GITATTRIBUTES_REL: &str = ".socket/vendor/maven2/.gitattributes";
28+
/// The tree root's `.gitignore` (`!*`), shared with sbt: re-includes the
29+
/// vendored jars against a user's `*.jar` rule (Java.gitignore), #1061.
30+
pub use super::sbt::TREE_GITIGNORE_REL as GITIGNORE_REL;
2831
const OFFLINE_LINE: &str = "-Daether.offline.protocols=file";
2932
const OFFLINE_KEY: &str = "-Daether.offline.protocols=";
3033
const TAIL_KEY: &str = "-Dmaven.repo.local.tail=";
@@ -313,6 +316,12 @@ pub fn plan_with_external(
313316
));
314317
}
315318
records.push(owned_file(read, GITATTRIBUTES_REL, &mut writes));
319+
records.push(super::owned_file_with(
320+
read,
321+
GITIGNORE_REL,
322+
super::coursier_tree::GITIGNORE.as_bytes(),
323+
&mut writes,
324+
));
316325
let (tree_dir, jar_rel, tree) = tree_writes(patch, &sv, suffixed_pom);
317326
writes.extend(tree);
318327

@@ -414,15 +423,17 @@ pub fn unplan(read: ReadFn<'_>, c: &Coords<'_>, records: &[WiringRecord]) -> Jvm
414423
before.insert(MAVEN_CONFIG.to_string(), Some(text));
415424
}
416425
}
417-
let created = records.iter().any(|w| {
418-
w.kind == OWNED_FILE_KIND && w.file == GITATTRIBUTES_REL && op_of(w) == "create"
419-
});
420-
if created && read(GITATTRIBUTES_REL).as_deref() == Some(TREE_GITATTRIBUTES.as_bytes()) {
421-
before.insert(
422-
GITATTRIBUTES_REL.to_string(),
423-
Some(TREE_GITATTRIBUTES.to_string()),
424-
);
425-
after.insert(GITATTRIBUTES_REL.to_string(), None);
426+
for (rel, body) in [
427+
(GITATTRIBUTES_REL, TREE_GITATTRIBUTES),
428+
(GITIGNORE_REL, super::coursier_tree::GITIGNORE),
429+
] {
430+
let created = records
431+
.iter()
432+
.any(|w| w.kind == OWNED_FILE_KIND && w.file == rel && op_of(w) == "create");
433+
if created && read(rel).as_deref() == Some(body.as_bytes()) {
434+
before.insert(rel.to_string(), Some(body.to_string()));
435+
after.insert(rel.to_string(), None);
436+
}
426437
}
427438
}
428439
JvmUnplan {

‎crates/socket-patch-core/src/vendor/jvm/mod.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,17 @@ pub enum Shape {
230230
Other,
231231
}
232232

233+
/// The committed vendor trees a plan for `shape` writes into.
234+
pub(crate) fn shape_trees(shape: Shape) -> &'static [&'static str] {
235+
match shape {
236+
Shape::MavenReactor | Shape::Sbt => &[layout::MAVEN2_TREE],
237+
Shape::Gradle => &[layout::GRADLE_TREE],
238+
Shape::Mixed => &[layout::MAVEN2_TREE, layout::GRADLE_TREE],
239+
Shape::ScalaCli => &[layout::COURSIER_TREE],
240+
Shape::Other => &[],
241+
}
242+
}
243+
233244
/// A planned file: project-relative forward-slash path and its full new
234245
/// bytes.
235246
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -589,6 +600,7 @@ pub(crate) fn eol_blind(rel: &str) -> bool {
589600
gradle::SCRIPT_REL,
590601
gradle::INDEX_REL,
591602
gradle::GITATTRIBUTES_REL,
603+
gradle::GITIGNORE_REL,
592604
gradle::SCRIPT_GITATTRIBUTES_REL,
593605
gradle::VENDOR_GITATTRIBUTES_REL,
594606
maven_reactor::GITATTRIBUTES_REL,

0 commit comments

Comments
 (0)