Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package elixir for openSUSE:Factory checked 
in at 2025-11-28 16:54:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/elixir (Old)
 and      /work/SRC/openSUSE:Factory/.elixir.new.14147 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "elixir"

Fri Nov 28 16:54:15 2025 rev:45 rq:1320429 version:1.19.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/elixir/elixir.changes    2025-11-14 
16:22:03.783358909 +0100
+++ /work/SRC/openSUSE:Factory/.elixir.new.14147/elixir.changes 2025-11-28 
16:56:41.391778608 +0100
@@ -1,0 +2,6 @@
+Fri Nov 28 08:40:24 UTC 2025 - Alessio Biancalana <[email protected]>
+
+- Upgrade to Elixir 1.19.4:
+  * Changelog available at https://hexdocs.pm/elixir/1.19.4/changelog.html
+
+-------------------------------------------------------------------

Old:
----
  elixir-1.19.3-doc.zip
  elixir-1.19.3.tar.gz

New:
----
  elixir-1.19.4-doc.zip
  elixir-1.19.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ elixir.spec ++++++
--- /var/tmp/diff_new_pack.6ylWYQ/_old  2025-11-28 16:56:42.435822567 +0100
+++ /var/tmp/diff_new_pack.6ylWYQ/_new  2025-11-28 16:56:42.439822735 +0100
@@ -18,7 +18,7 @@
 
 %define elixirdir %{_prefix}/lib/elixir
 Name:           elixir
-Version:        1.19.3
+Version:        1.19.4
 Release:        0
 Summary:        Functional meta-programming aware language built atop Erlang
 License:        Apache-2.0

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.6ylWYQ/_old  2025-11-28 16:56:42.579828630 +0100
+++ /var/tmp/diff_new_pack.6ylWYQ/_new  2025-11-28 16:56:42.591829135 +0100
@@ -1,6 +1,6 @@
-mtime: 1763057439
-commit: 6b8813e4b10bf2f8abee141647b9fe693b54932a8c6eede84f1493b2169d6853
+mtime: 1764319463
+commit: a85fc128d402651f852787b6a2f57d17c2aff5586354e645033c644cea34e928
 url: https://src.opensuse.org/erlang/elixir.git
-revision: 6b8813e4b10bf2f8abee141647b9fe693b54932a8c6eede84f1493b2169d6853
+revision: a85fc128d402651f852787b6a2f57d17c2aff5586354e645033c644cea34e928
 projectscmsync: https://src.opensuse.org/erlang/_ObsPrj.git
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2025-11-28 10:09:43.000000000 +0100
@@ -0,0 +1 @@
+.osc

++++++ elixir-1.19.3-doc.zip -> elixir-1.19.4-doc.zip ++++++
++++ 72567 lines of diff (skipped)

++++++ elixir-1.19.3.tar.gz -> elixir-1.19.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/CHANGELOG.md 
new/elixir-1.19.4/CHANGELOG.md
--- old/elixir-1.19.3/CHANGELOG.md      2025-11-13 17:33:43.000000000 +0100
+++ new/elixir-1.19.4/CHANGELOG.md      2025-11-27 16:50:26.000000000 +0100
@@ -234,6 +234,34 @@
 
 This work was performed by [Jonatan Männchen](https://maennchen.dev) and 
sponsored by the [Erlang Ecosystem Foundation](https://erlef.org).
 
+## v1.19.4 (2025-11-27)
+
+### 1. Enhancements
+
+#### Mix
+
+  * [mix xref] Add `--min-cycle-label` to help projects adapt to the more 
precise `mix xref graph` reports in Elixir v1.19. In previous versions, Elixir 
would break a large compilation cycle into several smaller ones, and therefore 
developers would check for `--min-cycle-size` on CI. However, the issue is not 
the size of the cycle (it has no implication in the amount of compiled files), 
but how many compile-time dependencies (aka compile labels) in a cycle. The new 
option allows developers to filter on the label parameter
+
+### 2. Bug fixes
+
+#### Elixir
+
+  * [File] Ensure `File.cp_r/3` reports non-existing destination properly 
(instead of source)
+
+#### ExUnit
+
+  * [ExUnit] Fix formatter crash when diffing takes too long
+  * [ExUnit] Ensure parallel matches in `assert` propagate type information
+
+#### Logger
+
+  * [Logger] Fix regression where formatter would crash when given chardata 
(the crash would happen when logging non-ASCII characters)
+
+#### Mix
+
+  * [mix help] Ensure `app:APP` works when the project or its dependencies 
were not yet compiled
+  * [mix escript.build] Ensure the `hex` application can be included in 
escripts
+
 ## v1.19.3 (2025-11-13)
 
 ### 1. Enhancements
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/VERSION new/elixir-1.19.4/VERSION
--- old/elixir-1.19.3/VERSION   2025-11-13 17:33:43.000000000 +0100
+++ new/elixir-1.19.4/VERSION   2025-11-27 16:50:26.000000000 +0100
@@ -1 +1 @@
-1.19.3
+1.19.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/bin/elixir new/elixir-1.19.4/bin/elixir
--- old/elixir-1.19.3/bin/elixir        2025-11-13 17:33:43.000000000 +0100
+++ new/elixir-1.19.4/bin/elixir        2025-11-27 16:50:26.000000000 +0100
@@ -6,7 +6,7 @@
 
 set -e
 
-ELIXIR_VERSION=1.19.3
+ELIXIR_VERSION=1.19.4
 
 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; 
}; }; then
   cat <<USAGE >&2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/bin/elixir.bat 
new/elixir-1.19.4/bin/elixir.bat
--- old/elixir-1.19.3/bin/elixir.bat    2025-11-13 17:33:43.000000000 +0100
+++ new/elixir-1.19.4/bin/elixir.bat    2025-11-27 16:50:26.000000000 +0100
@@ -4,7 +4,7 @@
 :: SPDX-FileCopyrightText: 2021 The Elixir Team
 :: SPDX-FileCopyrightText: 2012 Plataformatec
 
-set ELIXIR_VERSION=1.19.3
+set ELIXIR_VERSION=1.19.4
 
 if    ""%1""==""""                if ""%2""=="""" goto documentation
 if /I ""%1""==""--help""          if ""%2""=="""" goto documentation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/elixir/lib/code.ex 
new/elixir-1.19.4/lib/elixir/lib/code.ex
--- old/elixir-1.19.3/lib/elixir/lib/code.ex    2025-11-13 17:33:43.000000000 
+0100
+++ new/elixir-1.19.4/lib/elixir/lib/code.ex    2025-11-27 16:50:26.000000000 
+0100
@@ -1090,7 +1090,7 @@
   @doc since: "1.6.0"
   @spec format_string!(binary, [format_opt]) :: iodata
   def format_string!(string, opts \\ []) when is_binary(string) and 
is_list(opts) do
-    line_length = Keyword.get(opts, :line_length, 98)
+    {line_length, opts} = Keyword.pop(opts, :line_length, 98)
 
     to_quoted_opts =
       [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/elixir/lib/file.ex 
new/elixir-1.19.4/lib/elixir/lib/file.ex
--- old/elixir-1.19.3/lib/elixir/lib/file.ex    2025-11-13 17:33:43.000000000 
+0100
+++ new/elixir-1.19.4/lib/elixir/lib/file.ex    2025-11-27 16:50:26.000000000 
+0100
@@ -1224,7 +1224,13 @@
   defp do_cp_r(src, dest, on_conflict, dereference?, acc) when is_list(acc) do
     case :elixir_utils.read_link_type(src) do
       {:ok, :regular} ->
-        do_cp_file(src, dest, on_conflict, acc)
+        case do_cp_file(src, dest, on_conflict, acc) do
+          # we don't have a way to make a distinction between a non-existing 
src
+          # or dest being a non-existing dir in the case of :enoent,
+          # but we already know that src exists here.
+          {:error, :enoent, _} -> {:error, :enoent, dest}
+          other -> other
+        end
 
       {:ok, :symlink} ->
         case :file.read_link(src) do
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/elixir/lib/macro.ex 
new/elixir-1.19.4/lib/elixir/lib/macro.ex
--- old/elixir-1.19.3/lib/elixir/lib/macro.ex   2025-11-13 17:33:43.000000000 
+0100
+++ new/elixir-1.19.4/lib/elixir/lib/macro.ex   2025-11-27 16:50:26.000000000 
+0100
@@ -959,8 +959,9 @@
   backwards compatibility purposes.
 
   In future releases, Elixir may introduce truly required struct
-  fields, and therefore only one of required or default will be
-  present. Your code should prepare for such scenario accordingly.
+  fields, the required field will be removed and default will be
+  present only if the field is optional. Your code should prepare
+  for such scenario accordingly.
   """
   @doc since: "1.18.0"
   @spec struct_info!(module(), Macro.Env.t()) ::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/elixir/lib/module.ex 
new/elixir-1.19.4/lib/elixir/lib/module.ex
--- old/elixir-1.19.3/lib/elixir/lib/module.ex  2025-11-13 17:33:43.000000000 
+0100
+++ new/elixir-1.19.4/lib/elixir/lib/module.ex  2025-11-27 16:50:26.000000000 
+0100
@@ -402,30 +402,21 @@
 
   Accepts the function name (as an atom) of a function in the current module.
   The function must have an arity of 0 (no arguments). If the function does
-  not return `:ok`, the loading of the module will be aborted.
-  For example:
+  not return `:ok`, the loading of the module will be aborted. Its primary
+  use case is to load [NIFs](https://www.erlang.org/doc/man/erl_nif):
 
       defmodule MyModule do
-        @on_load :load_check
+        @on_load :load_external_code
 
-        def load_check do
-          if some_condition() do
-            :ok
-          else
-            :abort
-          end
-        end
-
-        def some_condition do
-          false
+        def load_external_code do
+          :erlang.load_nif(~c"path/to/extension.so_or_dll")
         end
       end
 
   The function given to `on_load` should avoid calling functions from
-  other modules. If you must call functions in other modules and those
-  modules are defined within the same project, the called modules must
-  have the `@compile {:autoload, true}` annotation, so they are loaded
-  upfront (and not from within the `@on_load` callback).
+  other modules. This is because, when running a `mix release`,
+  `on_load` runs extremely early, before any application starts running,
+  and therefore even systems like the `Logger` and `IO` are not yet available.
 
   ### `@vsn`
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/elixir/lib/record.ex 
new/elixir-1.19.4/lib/elixir/lib/record.ex
--- old/elixir-1.19.3/lib/elixir/lib/record.ex  2025-11-13 17:33:43.000000000 
+0100
+++ new/elixir-1.19.4/lib/elixir/lib/record.ex  2025-11-27 16:50:26.000000000 
+0100
@@ -27,8 +27,9 @@
 
   ## Types
 
-  Types can be defined for tuples with the `record/2` macro (only available in
-  typespecs). This macro will expand to a tuple as seen in the example below:
+  Types can be defined for tuples with the `record/2` construct (which is only
+  available in typespecs), with the record name as an atom and a keyword list
+  of fields and their types as argument:
 
       defmodule MyModule do
         require Record
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/elixir/lib/regex.ex 
new/elixir-1.19.4/lib/elixir/lib/regex.ex
--- old/elixir-1.19.3/lib/elixir/lib/regex.ex   2025-11-13 17:33:43.000000000 
+0100
+++ new/elixir-1.19.4/lib/elixir/lib/regex.ex   2025-11-27 16:50:26.000000000 
+0100
@@ -101,7 +101,7 @@
     * `:ungreedy` (U) - inverts the "greediness" of the regexp
       (the previous `r` option is deprecated in favor of `U`)
 
-    * `:export` (E) (since Elixir 1.20) - uses an exported pattern
+    * `:export` (E) (since Elixir 1.19.3) - uses an exported pattern
       which can be shared across nodes or through config, at the cost of a 
runtime
       overhead every time to re-import it every time it is executed.
       This modifier only has an effect starting on Erlang/OTP 28, and it is 
ignored
@@ -597,6 +597,7 @@
   end
 
   @compile {:inline, maybe_import_pattern: 1}
+  @compile {:no_warn_undefined, {:re, :import, 1}}
   defp maybe_import_pattern({:re_exported_pattern, _, _, _, _} = exported),
     do: :re.import(exported)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/elixir-1.19.3/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md 
new/elixir-1.19.4/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md
--- old/elixir-1.19.3/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md  
2025-11-13 17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md  
2025-11-27 16:50:26.000000000 +0100
@@ -228,7 +228,7 @@
 ```elixir
 defp loop_acceptor(socket) do
   {:ok, client} = :gen_tcp.accept(socket)
-  {:ok, pid} = Task.Supervisor.start_child(KV.BucketSupervisor, fn -> 
serve(client) end)
+  {:ok, pid} = Task.Supervisor.start_child(KV.ServerSupervisor, fn -> 
serve(client) end)
   :ok = :gen_tcp.controlling_process(client, pid)
   loop_acceptor(socket)
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/elixir/test/elixir/file_test.exs 
new/elixir-1.19.4/lib/elixir/test/elixir/file_test.exs
--- old/elixir-1.19.3/lib/elixir/test/elixir/file_test.exs      2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/elixir/test/elixir/file_test.exs      2025-11-27 
16:50:26.000000000 +0100
@@ -787,6 +787,18 @@
       end
     end
 
+    test "cp_r! with file src and dest unknown" do
+      src = fixture_path("cp_r/a/1.txt")
+      dest = tmp_path("tmp/unknown/")
+
+      message =
+        "could not copy recursively from #{inspect(src)} to #{inspect(dest)}. 
#{dest}: no such file or directory"
+
+      assert_raise File.CopyError, message, fn ->
+        File.cp_r!(src, dest)
+      end
+    end
+
     test "cp preserves mode" do
       File.mkdir_p!(tmp_path("tmp"))
       src = fixture_path("cp_mode")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/elixir-1.19.3/lib/elixir/test/elixir/fixtures/dialyzer/regressions.ex 
new/elixir-1.19.4/lib/elixir/test/elixir/fixtures/dialyzer/regressions.ex
--- old/elixir-1.19.3/lib/elixir/test/elixir/fixtures/dialyzer/regressions.ex   
1970-01-01 01:00:00.000000000 +0100
+++ new/elixir-1.19.4/lib/elixir/test/elixir/fixtures/dialyzer/regressions.ex   
2025-11-27 16:50:26.000000000 +0100
@@ -0,0 +1,14 @@
+defmodule Dialyzer.Regressions do
+  def io_inspect_opts do
+    IO.inspect(123, label: "foo", limit: :infinity)
+  end
+
+  def format_opts do
+    Code.format_string!("",
+      line_length: 120,
+      force_do_end_blocks: true,
+      locals_without_parens: true,
+      migrate: true
+    )
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/elixir-1.19.3/lib/elixir/test/elixir/kernel/dialyzer_test.exs 
new/elixir-1.19.4/lib/elixir/test/elixir/kernel/dialyzer_test.exs
--- old/elixir-1.19.3/lib/elixir/test/elixir/kernel/dialyzer_test.exs   
2025-11-13 17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/elixir/test/elixir/kernel/dialyzer_test.exs   
2025-11-27 16:50:26.000000000 +0100
@@ -185,6 +185,11 @@
     assert_dialyze_no_warnings!(context)
   end
 
+  test "no warning in various non-regression cases", context do
+    copy_beam!(context, Dialyzer.Regressions)
+    assert_dialyze_no_warnings!(context)
+  end
+
   defp copy_beam!(context, module) do
     name = "#{module}.beam"
     File.cp!(Path.join(context.base_dir, name), Path.join(context.outdir, 
name))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/ex_unit/lib/ex_unit/assertions.ex 
new/elixir-1.19.4/lib/ex_unit/lib/ex_unit/assertions.ex
--- old/elixir-1.19.3/lib/ex_unit/lib/ex_unit/assertions.ex     2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/ex_unit/lib/ex_unit/assertions.ex     2025-11-27 
16:50:26.000000000 +0100
@@ -160,6 +160,7 @@
         end
       end
 
+    {left, right} = move_match(left, right)
     __match__(left, right, code, check, __CALLER__)
   end
 
@@ -380,6 +381,12 @@
     {ExUnit.AssertionError.no_value(), expr}
   end
 
+  defp move_match(left, {:=, meta, [middle, right]}),
+    do: move_match({:=, meta, [left, middle]}, right)
+
+  defp move_match(left, right),
+    do: {left, right}
+
   @doc false
   def __match__({:when, _, _} = left, right, _, _, _) do
     suggestion =
@@ -409,7 +416,7 @@
           case right do
             unquote(left) ->
               unquote(check)
-              unquote(mark_as_generated(vars))
+              {unquote_splicing(mark_as_generated(vars))}
 
             _ ->
               left = unquote(Macro.escape(left))
@@ -427,7 +434,7 @@
     quote do
       right = unquote(right)
       expr = unquote(code)
-      unquote(vars) = unquote(match_expr)
+      {unquote_splicing(vars)} = unquote(match_expr)
       right
     end
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/ex_unit/lib/ex_unit/doc_test.ex 
new/elixir-1.19.4/lib/ex_unit/lib/ex_unit/doc_test.ex
--- old/elixir-1.19.3/lib/ex_unit/lib/ex_unit/doc_test.ex       2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/ex_unit/lib/ex_unit/doc_test.ex       2025-11-27 
16:50:26.000000000 +0100
@@ -964,7 +964,14 @@
 
   @doc false
   defmacro __assert__({:=, _, [left, right]} = assertion) do
+    {left, right} = move_match(left, right)
     code = Macro.escape(assertion, prune_metadata: true)
     ExUnit.Assertions.__match__(left, right, code, :ok, __CALLER__)
   end
+
+  defp move_match(left, {:=, meta, [middle, right]}),
+    do: move_match({:=, meta, [left, middle]}, right)
+
+  defp move_match(left, right),
+    do: {left, right}
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/ex_unit/lib/ex_unit/formatter.ex 
new/elixir-1.19.4/lib/ex_unit/lib/ex_unit/formatter.ex
--- old/elixir-1.19.3/lib/ex_unit/lib/ex_unit/formatter.ex      2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/ex_unit/lib/ex_unit/formatter.ex      2025-11-27 
16:50:26.000000000 +0100
@@ -513,10 +513,15 @@
 
   defp format_diff(left, right, context, formatter) do
     if has_value?(left) and has_value?(right) do
-      {result, env} = find_diff(left, right, context)
-      result = if formatter.(:diff_enabled?, false), do: result
-      hints = Enum.map(env.hints, &{:hint, format_hint(&1)})
-      {result, hints}
+      case find_diff(left, right, context) do
+        {result, env} ->
+          result = if formatter.(:diff_enabled?, false), do: result
+          hints = Enum.map(env.hints, &{:hint, format_hint(&1)})
+          {result, hints}
+
+        nil ->
+          {nil, []}
+      end
     else
       {nil, []}
     end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/elixir-1.19.3/lib/ex_unit/test/ex_unit/assertions_test.exs 
new/elixir-1.19.4/lib/ex_unit/test/ex_unit/assertions_test.exs
--- old/elixir-1.19.3/lib/ex_unit/test/ex_unit/assertions_test.exs      
2025-11-13 17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/ex_unit/test/ex_unit/assertions_test.exs      
2025-11-27 16:50:26.000000000 +0100
@@ -246,6 +246,16 @@
     end
   end
 
+  test "assert parallel match" do
+    assert %URI{} = uri = apply(URI, :parse, ["/foo"])
+    # This should not warn
+    assert %URI{uri | path: "/bar"}.path == "/bar"
+
+    assert uri = %URI{} = apply(URI, :parse, ["/foo"])
+    # This should not warn
+    assert %URI{uri | path: "/bar"}.path == "/bar"
+  end
+
   test "assert match with pinned variable" do
     a = 1
     {2, 1} = assert {2, ^a} = Value.tuple()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/logger/lib/logger/formatter.ex 
new/elixir-1.19.4/lib/logger/lib/logger/formatter.ex
--- old/elixir-1.19.3/lib/logger/lib/logger/formatter.ex        2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/logger/lib/logger/formatter.ex        2025-11-27 
16:50:26.000000000 +0100
@@ -244,26 +244,38 @@
     case if(is_function(enabled, 0), do: enabled.(), else: enabled) do
       true ->
         color = md[:ansi_color] || Map.fetch!(colors, level)
-        fragment = IO.ANSI.format_fragment(color, true)
-        data = IO.iodata_to_binary(data)
-        size = byte_size(data)
-
-        cond do
-          :binary.at(data, size - 2) == ?\r and :binary.at(data, size - 1) == 
?\n ->
-            [fragment, binary_part(data, 0, size - 2), IO.ANSI.reset() | 
"\r\n"]
+        [IO.ANSI.format_fragment(color, true), add_reset(data)]
 
-          :binary.at(data, size - 1) == ?\n ->
-            [fragment, binary_part(data, 0, size - 1), IO.ANSI.reset(), ?\n]
+      false ->
+        data
+    end
+  end
+
+  defp add_reset(binary) when is_binary(binary) do
+    size = byte_size(binary)
 
-          true ->
-            [fragment, data | IO.ANSI.reset()]
+    cond do
+      binary == "" ->
+        IO.ANSI.reset()
+
+      :binary.at(binary, size - 1) == ?\n ->
+        if size > 1 and :binary.at(binary, size - 2) == ?\r do
+          [binary_part(binary, 0, size - 2), IO.ANSI.reset() | "\r\n"]
+        else
+          [binary_part(binary, 0, size - 1), IO.ANSI.reset(), ?\n]
         end
 
-      false ->
-        data
+      true ->
+        [binary | IO.ANSI.reset()]
     end
   end
 
+  defp add_reset([?\r, ?\n]), do: [IO.ANSI.reset(), ?\r, ?\n]
+  defp add_reset([?\n]), do: [IO.ANSI.reset(), ?\n]
+  defp add_reset([last]), do: add_reset(last)
+  defp add_reset([h | t]), do: [h | add_reset(t)]
+  defp add_reset(rest), do: [rest | IO.ANSI.reset()]
+
   @doc """
   Formats the message of a log event.
   """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/elixir-1.19.3/lib/logger/test/logger/formatter_test.exs 
new/elixir-1.19.4/lib/logger/test/logger/formatter_test.exs
--- old/elixir-1.19.3/lib/logger/test/logger/formatter_test.exs 2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/logger/test/logger/formatter_test.exs 2025-11-27 
16:50:26.000000000 +0100
@@ -105,6 +105,14 @@
           colors: [enabled: true]
         )
 
+      assert %{level: :warning, msg: {:string, ""}, meta: %{}}
+             |> format(formatter)
+             |> IO.chardata_to_string() == "\e[33m\e[0m"
+
+      assert %{level: :warning, msg: {:string, "s"}, meta: %{}}
+             |> format(formatter)
+             |> IO.chardata_to_string() == "\e[33ms\e[0m"
+
       assert %{level: :warning, msg: {:string, "message"}, meta: %{}}
              |> format(formatter)
              |> IO.chardata_to_string() == "\e[33mmessage\e[0m"
@@ -116,6 +124,18 @@
       assert %{level: :warning, msg: {:string, "message\r\n"}, meta: %{}}
              |> format(formatter)
              |> IO.chardata_to_string() == "\e[33mmessage\e[0m\r\n"
+
+      assert %{level: :warning, msg: {:string, [?é, ["message", [?\n]]]}, 
meta: %{}}
+             |> format(formatter)
+             |> IO.chardata_to_string() == "\e[33mémessage\e[0m\n"
+
+      assert %{level: :warning, msg: {:string, [?é, "message", ?\r, ?\n]}, 
meta: %{}}
+             |> format(formatter)
+             |> IO.chardata_to_string() == "\e[33mémessage\e[0m\r\n"
+
+      assert %{level: :warning, msg: {:string, [?é, "message" | "\r\n"]}, 
meta: %{}}
+             |> format(formatter)
+             |> IO.chardata_to_string() == "\e[33mémessage\e[0m\r\n"
     end
   end
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/mix/lib/mix/tasks/cmd.ex 
new/elixir-1.19.4/lib/mix/lib/mix/tasks/cmd.ex
--- old/elixir-1.19.3/lib/mix/lib/mix/tasks/cmd.ex      2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/mix/lib/mix/tasks/cmd.ex      2025-11-27 
16:50:26.000000000 +0100
@@ -26,7 +26,7 @@
   Your shell will expand "lib/*" and then pass multiple arguments to
   `mix cmd`, which in turn passes them to `echo`. Note that, `mix cmd`
   by itself, does not perform any shell expansion. This means that,
-  if you invoke `mix cmd` programatically, as in:
+  if you invoke `mix cmd` programmatically, as in:
 
       Mix.Task.run("cmd", ["echo", "lib/*"])
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/mix/lib/mix/tasks/escript.build.ex 
new/elixir-1.19.4/lib/mix/lib/mix/tasks/escript.build.ex
--- old/elixir-1.19.3/lib/mix/lib/mix/tasks/escript.build.ex    2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/mix/lib/mix/tasks/escript.build.ex    2025-11-27 
16:50:26.000000000 +0100
@@ -271,7 +271,8 @@
     []
   end
 
-  defp extra_apps_in_app_tree(app) when app in [:eex, :ex_unit, :iex, :logger, 
:mix] do
+  # We include hex in here as it is always an archive and it cannot be 
depended on
+  defp extra_apps_in_app_tree(app) when app in [:eex, :ex_unit, :iex, :logger, 
:mix, :hex] do
     [app]
   end
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/mix/lib/mix/tasks/help.ex 
new/elixir-1.19.4/lib/mix/lib/mix/tasks/help.ex
--- old/elixir-1.19.3/lib/mix/lib/mix/tasks/help.ex     2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/mix/lib/mix/tasks/help.ex     2025-11-27 
16:50:26.000000000 +0100
@@ -112,6 +112,7 @@
 
   def run(["app:" <> app]) do
     loadpaths!()
+
     app = String.to_atom(app)
 
     # If the application is not available, attempt to load it from 
Erlang/Elixir
@@ -119,7 +120,11 @@
       try do
         Mix.ensure_application!(app)
       rescue
-        _ -> :ok
+        _ ->
+          # Otherwise, it may be a dep or the current project not yet compiled
+          if Mix.Project.get() do
+            Mix.Task.run("compile")
+          end
       end
     end
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/mix/lib/mix/tasks/xref.ex 
new/elixir-1.19.4/lib/mix/lib/mix/tasks/xref.ex
--- old/elixir-1.19.3/lib/mix/lib/mix/tasks/xref.ex     2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/mix/lib/mix/tasks/xref.ex     2025-11-27 
16:50:26.000000000 +0100
@@ -44,12 +44,22 @@
                   └── lib/a.ex
 
   Because you have a compile-time dependency, any of the files `lib/a.ex`,
-  `lib/b.ex`, and `lib/c.ex` depend on will cause the whole cycle to
-  recompile. Therefore, your first priority to reduce compile times is
-  to remove such cycles. You can spot them by running:
+  `lib/b.ex`, and `lib/c.ex` depend on will cause `lib/a.ex` to recompile.
+  In other words, whenever you have a cycle, **a change to any file in the
+  cycle will cause all compile-time deps to recompile**. Therefore, your
+  first priority to reduce constant recompilations is to remove them.
+  You can spot them by running:
 
       $ mix xref graph --format cycles --label compile-connected
 
+  > #### Use the --label option
+  >
+  > The job of `mix xref` is to explore relationships between files
+  > and it is expected that most of your files are either directly
+  > or indirectly connected. For this reason, it is strongly advised
+  > to pass the `--label` option to filter the amount of data,
+  > specifically with `compile-connected` (or `compile`) as values.
+
   Whenever you find a compile-time dependency, such as `lib/a.ex` pointing
   to `lib/b.ex`, there are two ways to remove them:
 
@@ -208,13 +218,11 @@
     * `--exclude` - path to exclude. Can be repeated to exclude multiple paths.
 
     * `--label` - only shows relationships with the given label.
-      The labels are "compile", "export" and "runtime". By default, the 
`--label`
-      option does not change how the graph is computed, it simply filters the
-      printed graph to show only relationships with the given label. However,
-      you can pass `--only-direct` to trim the graph to only the nodes that
-      have the direct relationship given by label. There is also a special
-      label called "compile-connected" that keeps only compile-time files with
-      at least one transitive dependency. See "Dependency types" section below.
+      The labels are "compile-connected", "compile", "export" and "runtime".
+      By default, the `--label` option does not change how the graph is 
computed,
+      it simply filters the printed graph to show only relationships with the 
given
+      label. However, you can pass `--only-direct` to trim the graph to only 
the
+      nodes that have the direct relationship given by label.
 
     * `--group` - provide comma-separated paths to consider as a group. 
Dependencies
       from and into multiple files of the group are considered a single 
dependency.
@@ -239,6 +247,9 @@
     * `--min-cycle-size` - controls the minimum cycle size on formats
       like `stats` and `cycles`
 
+    * `--min-cycle-label` - controls the minimum number of dependencies
+      with the given `--label` on a cycle
+
     * `--format` - can be set to one of:
 
       * `pretty` - prints the graph to the terminal using Unicode characters.
@@ -435,6 +446,7 @@
     sink: :keep,
     source: :keep,
     min_cycle_size: :integer,
+    min_cycle_label: :integer,
     output: :string
   ]
 
@@ -1218,9 +1230,22 @@
         cycles
       end
 
-    # :compile_connected is the same
+    min_cycle_label =
+      if integer = opts[:min_cycle_label] do
+        if filter == :all do
+          Mix.raise("--min-cycle-label requires the --label option to be 
given")
+        end
+
+        integer
+      else
+        1
+      end
+
+    # :compile_connected is the same as :compile
     if cycle_fn = cycle_filter_fn(filter) do
-      Enum.filter(cycles, fn {_length, cycle} -> Enum.any?(cycle, cycle_fn) 
end)
+      Enum.filter(cycles, fn {_length, cycle} ->
+        Enum.count_until(cycle, cycle_fn, min_cycle_label) == min_cycle_label
+      end)
     else
       cycles
     end
@@ -1269,11 +1294,35 @@
           shell.info("#{length(cycles)} cycles found. Showing them in 
decreasing size:\n")
 
           for {length, cycle} <- cycles do
-            shell.info("Cycle of length #{length}:\n")
+            meta =
+              cycle
+              |> Enum.reduce({0, 0}, fn
+                {_, :compile}, {compile, export} -> {compile + 1, export}
+                {_, :export}, {compile, export} -> {compile, export + 1}
+                {_, _}, {compile, export} -> {compile, export}
+              end)
+              |> case do
+                {0, 0} ->
+                  ""
+
+                {compile, export} ->
+                  info =
+                    if(compile > 0, do: ["#{compile} compile"], else: []) ++
+                      if(export > 0, do: ["#{export} export"], else: [])
+
+                  " (" <> Enum.join(info, ", ") <> ")"
+              end
+
+            shell.info("Cycle of length #{length}#{meta}:\n")
 
             for {node, type} <- cycle do
-              type = if type, do: " (#{type})", else: ""
-              shell.info("    " <> node <> type)
+              shell.info(
+                case type do
+                  :compile -> [:red, "    #{node} (compile)"]
+                  :export -> "    #{node} (export)"
+                  _ -> "    #{node}"
+                end
+              )
             end
 
             shell.info("")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/mix/test/mix/tasks/help_test.exs 
new/elixir-1.19.4/lib/mix/test/mix/tasks/help_test.exs
--- old/elixir-1.19.3/lib/mix/test/mix/tasks/help_test.exs      2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/mix/test/mix/tasks/help_test.exs      2025-11-27 
16:50:26.000000000 +0100
@@ -222,6 +222,41 @@
     end)
   end
 
+  defmodule ExampleProject do
+    def project do
+      [
+        app: :sample,
+        version: "0.1.0"
+      ]
+    end
+  end
+
+  test "help app:APP for current project", context do
+    in_tmp(context.test, fn ->
+      Mix.Project.push(ExampleProject)
+      File.mkdir_p!("lib")
+
+      File.write!("lib/example.ex", ~s'''
+      defmodule Example do
+        @moduledoc """
+        This is an example module.
+        """
+      end
+      ''')
+
+      output =
+        capture_io(fn ->
+          Mix.Tasks.Help.run(["app:sample"])
+        end)
+
+      assert output =~ """
+             # Example
+
+             This is an example module.
+             """
+    end)
+  end
+
   test "help Elixir MODULE", context do
     in_tmp(context.test, fn ->
       output =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.19.3/lib/mix/test/mix/tasks/xref_test.exs 
new/elixir-1.19.4/lib/mix/test/mix/tasks/xref_test.exs
--- old/elixir-1.19.3/lib/mix/test/mix/tasks/xref_test.exs      2025-11-13 
17:33:43.000000000 +0100
+++ new/elixir-1.19.4/lib/mix/test/mix/tasks/xref_test.exs      2025-11-27 
16:50:26.000000000 +0100
@@ -500,7 +500,7 @@
       assert_graph(["--format", "cycles"], """
       1 cycles found. Showing them in decreasing size:
 
-      Cycle of length 2:
+      Cycle of length 2 (1 compile):
 
           lib/a.ex (compile)
           lib/b.ex
@@ -512,7 +512,7 @@
       assert_graph(["--format", "cycles", "--label", "compile"], """
       1 cycles found. Showing them in decreasing size:
 
-      Cycle of length 2:
+      Cycle of length 2 (1 compile):
 
           lib/a.ex (compile)
           lib/b.ex
@@ -524,7 +524,7 @@
       assert_graph(["--format", "cycles", "--label", "compile-connected"], """
       1 cycles found. Showing them in decreasing size:
 
-      Cycle of length 2:
+      Cycle of length 2 (1 compile):
 
           lib/a.ex (compile)
           lib/b.ex
@@ -539,7 +539,7 @@
         assert_graph(["--format", "cycles", "--fail-above", "0"], """
         1 cycles found. Showing them in decreasing size:
 
-        Cycle of length 2:
+        Cycle of length 2 (1 compile):
 
             lib/a.ex (compile)
             lib/b.ex
@@ -548,11 +548,23 @@
       end
     end
 
+    test "cycles with min_cycle_label matching actual length" do
+      assert_graph(["--format", "cycles", "--label", "compile", 
"--min-cycle-label", "1"], """
+      1 cycles found. Showing them in decreasing size:
+
+      Cycle of length 2 (1 compile):
+
+          lib/a.ex (compile)
+          lib/b.ex
+
+      """)
+    end
+
     test "cycles with min_cycle_size matching actual length" do
       assert_graph(["--format", "cycles", "--min-cycle-size", "2"], """
       1 cycles found. Showing them in decreasing size:
 
-      Cycle of length 2:
+      Cycle of length 2 (1 compile):
 
           lib/a.ex (compile)
           lib/b.ex
@@ -560,6 +572,12 @@
       """)
     end
 
+    test "cycles with min_cycle_label greater than actual length" do
+      assert_graph(["--format", "cycles", "--label", "compile", 
"--min-cycle-label", "2"], """
+      No cycles found
+      """)
+    end
+
     test "cycles with min_cycle_size greater than actual length" do
       assert_graph(["--format", "cycles", "--min-cycle-size", "3"], """
       No cycles found

Reply via email to