Repository: conan-io/conan
Pull request #16137 (2024-04-23 22:49:43)

fix conflict message
Changelog: Fix: Improved error message when there are conflicts in the graph.
Docs: Omit

Close https://github.com/conan-io/conan/issues/16130



Patch:
diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py
index 3f4f8ada7..bf591c461 100644
--- a/conan/cli/commands/graph.py
+++ b/conan/cli/commands/graph.py
@@ -14,9 +14,8 @@ from conan.errors import ConanException
 from conan.internal.deploy import do_deploys
 from conans.client.graph.graph import BINARY_MISSING
 from conans.client.graph.install_graph import InstallGraph
-from conans.errors import ConanConnectionError, NotFoundException
+from conans.errors import NotFoundException
 from conans.model.recipe_ref import ref_matches, RecipeReference
-from conans.model.version_range import VersionRange
 
 
 def explain_formatter_text(data):
@@ -214,10 +213,7 @@ def graph_info(conan_api, parser, subparser, *args):
                                                          remotes, args.update,
                                                          check_updates=args.check_updates)
     print_graph_basic(deps_graph)
-    if deps_graph.error:
-        ConanOutput().info("Graph error", Color.BRIGHT_RED)
-        ConanOutput().info("    {}".format(deps_graph.error), Color.BRIGHT_RED)
-    else:
+    if not deps_graph.error:
         conan_api.graph.analyze_binaries(deps_graph, args.build, remotes=remotes, update=args.update,
                                          lockfile=lockfile)
         print_graph_packages(deps_graph)
diff --git a/conans/client/graph/graph_error.py b/conans/client/graph/graph_error.py
index b29e84fb2..cf2497e35 100644
--- a/conans/client/graph/graph_error.py
+++ b/conans/client/graph/graph_error.py
@@ -25,19 +25,15 @@ class GraphConflictError(GraphError):
                             "require": self.require.serialize()}}
 
     def __str__(self):
-        if self.node.ref is not None and self.base_previous.ref is not None:
-            return f"Version conflict: {self.node.ref}->{self.require.ref}, " \
-                   f"{self.base_previous.ref}->{self.prev_require.ref}."
-        else:
-            conflicting_node = self.node.ref or self.base_previous.ref
-            conflicting_node_msg = ""
-            if conflicting_node is not None:
-                conflicting_node_msg = f"\nConflict originates from {conflicting_node}\n"
-            return f"Version conflict: " \
-                   f"Conflict between {self.require.ref} and {self.prev_require.ref} in the graph." \
-                   f"{conflicting_node_msg}" \
-                   f"\nRun conan graph info with your recipe and add --format=html " \
-                   f"to inspect the graph errors in an easier to visualize way."
+        conflicting_node = self.node.ref or self.base_previous.ref
+        conflicting_node_msg = ""
+        if conflicting_node is not None:
+            conflicting_node_msg = f"\nConflict originates from {conflicting_node}\n"
+        return f"Version conflict: " \
+               f"Conflict between {self.require.ref} and {self.prev_require.ref} in the graph." \
+               f"{conflicting_node_msg}" \
+               f"\nRun 'conan graph info ... --format=html > graph.html' " \
+               f"and open 'graph.html' to inspect the conflict graphically."
 
 
 class GraphLoopError(GraphError):


Test patch:
diff --git a/conans/test/integration/command/info/test_graph_info_graphical.py b/conans/test/integration/command/info/test_graph_info_graphical.py
index 29dfe14b4..6637f2299 100644
--- a/conans/test/integration/command/info/test_graph_info_graphical.py
+++ b/conans/test/integration/command/info/test_graph_info_graphical.py
@@ -131,7 +131,7 @@ def test_graph_info_html_error_reporting_output():
 
     tc.run("graph info --requires=math/1.0 --requires=ui/1.0 --format=html", assert_error=True,
            redirect_stdout="graph.html")
-    assert "ERROR: Version conflict:" in tc.out # check that it doesn't crash
+    assert "ERROR: Version conflict:" in tc.out  # check that it doesn't crash
 
     # change order,  just in case
     tc.run("graph info --requires=ui/1.0 --requires=math/1.0 --format=html", assert_error=True,
@@ -144,3 +144,20 @@ def test_graph_info_html_error_reporting_output():
     assert "ERROR: Version conflict:" in tc.out  # check that it doesn't crash
     # Check manually
     # tc.run_command(f"{tc.current_folder}/graph.html")
+
+
+def test_graph_conflict_diamond():
+    c = TestClient()
+    c.save({"math/conanfile.py": GenConanfile("math"),
+            "engine/conanfile.py": GenConanfile("engine", "1.0").with_requires("math/1.0"),
+            "ai/conanfile.py": GenConanfile("ai", "1.0").with_requires("math/1.0.1"),
+            "game/conanfile.py": GenConanfile("game", "1.0").with_requires("engine/1.0", "ai/1.0"),
+            })
+    c.run("create math --version=1.0")
+    c.run("create math --version=1.0.1")
+    c.run("create math --version=1.0.2")
+    c.run("create engine")
+    c.run("create ai")
+    c.run("graph info game --format=html", assert_error=True, redirect_stdout="graph.html")
+    # check that it doesn't crash
+    assert "ERROR: Version conflict: Conflict between math/1.0.1 and math/1.0 in the graph." in c.out
diff --git a/conans/test/integration/graph/conflict_diamond_test.py b/conans/test/integration/graph/conflict_diamond_test.py
index f55336fda..462bfecdd 100644
--- a/conans/test/integration/graph/conflict_diamond_test.py
+++ b/conans/test/integration/graph/conflict_diamond_test.py
@@ -23,7 +23,7 @@ class TestConflictDiamondTest:
         c.run("create engine")
         c.run("create ai")
         c.run("install game", assert_error=True)
-        assert "Version conflict: ai/1.0->math/1.0.1, game/1.0->math/1.0" in c.out
+        assert "Version conflict: Conflict between math/1.0.1 and math/1.0 in the graph" in c.out
         # This shouldnt error, so we are able to diagnose our dependency graph
         # The UX still need to be improved, but this is start
         c.run("graph info game --filter=requires", assert_error=True)
diff --git a/conans/test/integration/lockfile/test_graph_overrides.py b/conans/test/integration/lockfile/test_graph_overrides.py
index bae9f6817..6c64ecb1a 100644
--- a/conans/test/integration/lockfile/test_graph_overrides.py
+++ b/conans/test/integration/lockfile/test_graph_overrides.py
@@ -315,7 +315,7 @@ def test_introduced_conflict(override, force):
             })
 
     c.run("graph info pkgd --lockfile=pkgd/conan.lock --lockfile-partial", assert_error=True)
-    assert "Version conflict: pkgc/0.1->pkga/[>=0.2 <0.3], pkgd/0.1->pkga/0.1" in c.out
+    assert "Version conflict: Conflict between pkga/[>=0.2 <0.3] and pkga/0.1 in the graph" in c.out
     # Resolve the conflict with an override or force
     c.save({"pkgd/conanfile.py": GenConanfile("pkgd", "0.1").with_requirement("pkgb/0.1")
                                                             .with_requirement("pkgc/0.1")