Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 120 additions & 8 deletions src/install/PackageManager/PackageManagerEnqueue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,14 @@ pub fn enqueue_dependency_with_main_and_success_fn(
return Ok(());
}

// Second: the package an identical dependency literal resolved to
if let Some(pkg_id) =
find_locked_git_package(this, id, dependency, &dep, ResolutionTag::Git)
{
success_fn(this, id, pkg_id);
return Ok(());
}

// reshaped for borrowck — `alias`/`url` borrow
// `this.lockfile.buffers.string_bytes`; detach the slice
// lifetimes so the `&mut PackageManager` reborrows for the
Expand Down Expand Up @@ -1210,14 +1218,41 @@ pub fn enqueue_dependency_with_main_and_success_fn(
}

if let Some(repo_fd) = this.git_repositories.get(&clone_id).copied() {
let resolved = Repository::find_commit(
this.env_mut(),
this.log_mut(),
repo_fd,
alias,
this.lockfile.str(&dep.committish),
clone_id,
)?;
// A dependency already bound to a package checks out that
// package's locked commit. `find_commit` follows the ref,
// which may have moved since it was locked, and the isolated
// installer's store entry waits on the locked checkout id
// (the clone-failure drain in runTasks.rs keys the same way).
Comment thread
robobun marked this conversation as resolved.
let bound_resolved: Option<Vec<u8>> = {
let bound = this.lockfile.buffers.resolutions[id as usize];
if bound != invalid_package_id
&& (bound as usize) < this.lockfile.packages.len()
{
let bound_res = &this.lockfile.packages.items_resolution()[bound as usize];
if bound_res.tag == ResolutionTag::Git {
let locked = bound_res
.git()
.resolved
.slice(this.lockfile.buffers.string_bytes.as_slice());
(!locked.is_empty()).then(|| locked.to_vec())
} else {
None
}
} else {
None
}
};
let resolved = match bound_resolved {
Some(resolved) => resolved,
None => Repository::find_commit(
this.env_mut(),
this.log_mut(),
repo_fd,
alias,
this.lockfile.str(&dep.committish),
clone_id,
)?,
};
let checkout_id = Task::Id::for_git_checkout(url, &resolved);

let needs_ctx =
Expand Down Expand Up @@ -1292,6 +1327,14 @@ pub fn enqueue_dependency_with_main_and_success_fn(
return Ok(());
}

// Second: the package an identical dependency literal resolved to
if let Some(pkg_id) =
find_locked_git_package(this, id, dependency, dep, ResolutionTag::Github)
{
success_fn(this, id, pkg_id);
return Ok(());
}

let url = this.alloc_github_url(dep);
// url is Box<[u8]>; dropped at scope end
let task_id = Task::Id::for_tarball(&url);
Expand Down Expand Up @@ -1933,6 +1976,75 @@ fn update_name_and_name_hash_from_version_replacement(
}
}

/// The package an identical git/github dependency (same name and version
/// literal) is already bound to. `bun.lock` writes the resolved commit in the
/// committish position of the resolution string, so after a reload a branch,
/// tag, or bare ref never matches `get_package_id`'s committish comparison;
/// the unchanged dependency literal is the lossless record of the previous
/// resolution, and reusing its binding keeps re-resolution off the network.
Comment thread
robobun marked this conversation as resolved.
fn find_locked_git_package(
this: &PackageManager,
id: DependencyID,
dependency: &Dependency,
repo: &Repository,
resolution_tag: ResolutionTag,
) -> Option<PackageID> {
if this.lockfile.buffers.resolutions[id as usize] != invalid_package_id {
return None;
}

// An update target must re-resolve against the remote (same test as
// `Diff::generate`; an empty request list is a bare `bun update`).
Comment thread
robobun marked this conversation as resolved.
if this.to_update
&& (this.update_requests.is_empty()
|| this
.update_requests
.iter()
.any(|request| request.name_hash == dependency.name_hash))
{
return None;
}

let buf = this.lockfile.buffers.string_bytes.as_slice();
let package_resolutions = this.lockfile.packages.items_resolution();
let dependencies = this.lockfile.buffers.dependencies.as_slice();
let resolutions = this.lockfile.buffers.resolutions.as_slice();

for (other, &package_id) in dependencies.iter().zip(resolutions) {
if package_id == invalid_package_id || (package_id as usize) >= package_resolutions.len() {
continue;
}
if other.name_hash != dependency.name_hash
|| other.version.tag != dependency.version.tag
|| !other
.version
.literal
.eql(dependency.version.literal, buf, buf)
{
continue;
}
// Overrides/catalogs replace the version after parsing, so the bound
// package must also match the effective repository (changed overrides
// and catalogs invalidate old bindings before re-enqueueing).
Comment thread
robobun marked this conversation as resolved.
let resolution = &package_resolutions[package_id as usize];
if resolution.tag != resolution_tag {
continue;
}
// Byte-equal repos only. An scp-like repo ("git@host:path") is
// serialized with an "ssh://" prefix the dependency parse lacks, and
// every git task id downstream keys on these exact bytes, so binding
// across the two spellings would strand the isolated store's checkout
// waiter; scp dependencies keep the pre-reuse fetch path instead.
Comment thread
robobun marked this conversation as resolved.
let locked = resolution.repository();
if !locked.repo.eql(repo.repo, buf, buf) || !locked.owner.eql(repo.owner, buf, buf) {
continue;
Comment thread
robobun marked this conversation as resolved.
}
return Some(package_id);
}
Comment thread
robobun marked this conversation as resolved.

None
}

pub(crate) enum ResolvedPackageTask {
/// Pending network task to schedule
NetworkTask(*mut NetworkTask),
Expand Down
25 changes: 21 additions & 4 deletions src/install/PackageManager/install_with_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,12 +484,23 @@ pub fn install_with_manager(
// ever reads through a pointer into the old backing storage.
if manager.summary.overrides_changed && !all_name_hashes.is_empty() {
let dependencies_len = manager.lockfile.buffers.dependencies.len();
// Invalidate every affected resolution before enqueueing
// any of them: re-resolution consults existing bindings
// (`find_locked_git_package`), and a not-yet-cleared
// sibling would rebind the dependency to the package the
// old override resolved to.
Comment thread
robobun marked this conversation as resolved.
for dependency_i in 0..dependencies_len {
if all_name_hashes.contains(
&manager.lockfile.buffers.dependencies[dependency_i].name_hash,
) {
manager.lockfile.buffers.resolutions[dependency_i] =
invalid_package_id;
}
}
for dependency_i in 0..dependencies_len {
let dependency =
manager.lockfile.buffers.dependencies[dependency_i].clone();
if all_name_hashes.contains(&dependency.name_hash) {
manager.lockfile.buffers.resolutions[dependency_i] =
invalid_package_id;
if let Err(err) = enqueue_dependency_with_main(
manager,
dependency_i as u32,
Expand All @@ -505,6 +516,14 @@ pub fn install_with_manager(

if manager.summary.catalogs_changed {
let dependencies_len = manager.lockfile.buffers.dependencies.len();
// Same two-pass shape as the overrides loop above.
for dep_i in 0..dependencies_len {
if manager.lockfile.buffers.dependencies[dep_i].version.tag
== DependencyVersionTag::Catalog
{
manager.lockfile.buffers.resolutions[dep_i] = invalid_package_id;
}
}
for _dep_id in 0..dependencies_len {
let dep_id: DependencyID = u32::try_from(_dep_id).expect("int cast");
let dep =
Expand All @@ -513,8 +532,6 @@ pub fn install_with_manager(
continue;
}

manager.lockfile.buffers.resolutions[dep_id as usize] =
invalid_package_id;
if let Err(err) = enqueue_dependency_with_main(
manager,
dep_id,
Expand Down
Loading