From d95980c9f29c4c51928ba38c9311866d2aa8e0fe Mon Sep 17 00:00:00 2001 From: Alex Naser Date: Mon, 9 Feb 2026 12:02:15 +0100 Subject: [PATCH] Parallelize dep lock status checks during deps.loadpaths To make this safe, git.ex's lock_status no longer uses File.cd! (which mutates global process state), instead passing the `:cd` into System.cmd. --- lib/mix/lib/mix/scm/git.ex | 34 +++++++++++++------------ lib/mix/lib/mix/tasks/deps.loadpaths.ex | 6 ++++- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/mix/lib/mix/scm/git.ex b/lib/mix/lib/mix/scm/git.ex index 2cb1e262beb..4c5a9867b1f 100644 --- a/lib/mix/lib/mix/scm/git.ex +++ b/lib/mix/lib/mix/scm/git.ex @@ -75,15 +75,13 @@ defmodule Mix.SCM.Git do cond do lock_rev = get_lock_rev(lock, opts) -> - File.cd!(opts[:checkout], fn -> - %{origin: origin, rev: rev} = get_rev_info() + %{origin: origin, rev: rev} = get_rev_info(opts[:checkout]) - if get_lock_repo(lock) == origin and lock_rev == rev do - :ok - else - :mismatch - end - end) + if get_lock_repo(lock) == origin and lock_rev == rev do + :ok + else + :mismatch + end is_nil(lock) -> :mismatch @@ -333,11 +331,11 @@ defmodule Mix.SCM.Git do end end - defp get_rev_info do + defp get_rev_info(dir \\ nil) do # These commands can fail and we don't want to raise. origin_command = ["--git-dir=.git", "config", "remote.origin.url"] rev_command = ["--git-dir=.git", "rev-parse", "--verify", "--quiet", "HEAD"] - opts = cmd_opts([]) + opts = if dir, do: cmd_opts(cd: dir), else: cmd_opts([]) with {origin, 0} <- System.cmd("git", origin_command, opts), {rev, 0} <- System.cmd("git", rev_command, opts) do @@ -391,13 +389,17 @@ defmodule Mix.SCM.Git do end end - # Attempt to set the current working directory by default. - # This addresses an issue changing the working directory when executing from - # within a secondary node since file I/O is done through the main node. defp cmd_opts(opts) do - case File.cwd() do - {:ok, cwd} -> Keyword.put(opts, :cd, cwd) - _ -> opts + if Keyword.has_key?(opts, :cd) do + opts + else + # Attempt to set the current working directory by default. + # This addresses an issue changing the working directory when executing from + # within a secondary node since file I/O is done through the main node. + case File.cwd() do + {:ok, cwd} -> Keyword.put(opts, :cd, cwd) + _ -> opts + end end end diff --git a/lib/mix/lib/mix/tasks/deps.loadpaths.ex b/lib/mix/lib/mix/tasks/deps.loadpaths.ex index 1580b3682f7..f53396f9ba6 100644 --- a/lib/mix/lib/mix/tasks/deps.loadpaths.ex +++ b/lib/mix/lib/mix/tasks/deps.loadpaths.ex @@ -123,7 +123,11 @@ defmodule Mix.Tasks.Deps.Loadpaths do end defp deps_check(all, no_compile?) do - all = Enum.map(all, &check_lock/1) + all = + all + |> Task.async_stream(&check_lock/1, ordered: true, timeout: :infinity) + |> Enum.map(fn {:ok, dep} -> dep end) + {not_ok, to_compile} = partition(all, [], []) cond do