Repository: conan-io/conan
Pull request: conan search <pattern> --revisions with cache Issue/5472 
Changelog: Feature: Output current revision from references in local cache when using a pattern
Docs: https://github.com/conan-io/docs/pull/1381

Previous behaviour was that current revision number could only be shown if a reference was 
given with the `--revisions` argument but not a pattern. 
Now you can use a pattern to list the current revision of the references in the local cache.

Closes #5472

![revisions modified](https://user-images.githubusercontent.com/5045666/61947830-81e1c680-afa6-11e9-8099-e9c77f9d4fcd.gif)

- [X] Refer to the issue that supports this Pull Request.
- [X] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [X] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [X] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one. 

<sup>**Note:** By default this PR will skip the slower tests and will use a limited set of python versions. Check [here](https://github.com/conan-io/conan/blob/develop/.github/PR_INCREASE_TESTING.md) how to increase the testing level by writing some tags in the current PR body text.</sup>


Problem statement:
conan search <pattern> --revisions
I miss a list of references but with the current revision in the local cache. Why? Even having the revisions enabled, the "install" traces ~~don't show which RREV is being used~~ (edit: there is a trace P1/1.0@conan/stable: Downloaded recipe revision c9da69cad5ccb88452fa1c6ed3db54b2) but still would be practical. If you have missing packages and you want to "debug" why first you need to know at first sight the resolved revisions for the recipes.

Hints from issue discussion:

Commit message: add some tests

Patch to review:
diff --git a/conans/client/command.py b/conans/client/command.py
index 28eb53379d0..ed5eac8434d 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -1212,17 +1212,36 @@ def search(self, *args):
                 else:
                     info = self._conan.get_package_revisions(repr(pref), remote_name=args.remote)
 
-                if not info:
-                    if not ref:
-                        msg = "With --revision, specify a reference (e.g {ref}) or a package " \
-                              "reference with " \
-                              "recipe revision (e.g {ref}#3453453453:d50a0d523d98c15bb147b18f" \
-                              "a7d203887c38be8b)".format(ref=_REFERENCE_EXAMPLE)
-                        raise ConanException(msg)
-                    info = self._conan.get_recipe_revisions(repr(ref),
-                                                            remote_name=args.remote)
-                self._outputer.print_revisions(ref, info, remote_name=args.remote)
-                return
+                if not ref and not info:
+                    exc_msg = "With --revision, specify a reference (e.g {ref}) a valid pattern " \
+                          "or a package reference with " \
+                          "recipe revision (e.g {ref}#3453453453:d50a0d523d98c15bb147b18f" \
+                          "a7d203887c38be8b)".format(ref=_REFERENCE_EXAMPLE)
+                    if args.remote:
+                        raise ConanException(exc_msg)
+                    else:
+                        info = self._conan.search_recipes(args.pattern_or_reference,
+                                                          remote_name=args.remote,
+                                                          case_sensitive=args.case_sensitive)
+                        if info["results"]:
+                            for remote_info in info["results"]:
+                                for conan_item in remote_info["items"]:
+                                    reference = conan_item["recipe"]["id"]
+                                    ref = ConanFileReference.loads(reference)
+                                    rev = self._conan.get_recipe_revisions(repr(ref),
+                                                                           remote_name=args.remote,
+                                                                           check_rev_time=False)
+                                    self._outputer.print_revisions(ref, rev, remote_name=args.remote)
+                        else:
+                            raise ConanException(exc_msg)
+
+                    return
+                else:
+                    if not info:
+                        info = self._conan.get_recipe_revisions(repr(ref),
+                                                                remote_name=args.remote)
+                    self._outputer.print_revisions(ref, info, remote_name=args.remote)
+                    return
 
             if ref:
                 info = self._conan.search_packages(repr(ref), query=args.query,
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
index 404984f17f2..2bf6f3ec0a1 100644
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -1093,7 +1093,7 @@ def get_remote_by_name(self, remote_name):
         return self._cache.registry.load_remotes()[remote_name]
 
     @api_method
-    def get_recipe_revisions(self, reference, remote_name=None):
+    def get_recipe_revisions(self, reference, remote_name=None, check_rev_time=True):
         if not self._cache.config.revisions_enabled:
             raise ConanException("The client doesn't have the revisions feature enabled."
                                  " Enable this feature setting to '1' the environment variable"
@@ -1111,20 +1111,21 @@ def get_recipe_revisions(self, reference, remote_name=None):
                 e.print_rev = True
                 raise e
 
-            # Check the time in the associated remote if any
-            remote_name = layout.load_metadata().recipe.remote
-            remote = self._cache.registry.load_remotes()[remote_name] if remote_name else None
             rev_time = None
-            if remote:
-                try:
-                    revisions = self._remote_manager.get_recipe_revisions(ref, remote)
-                except RecipeNotFoundException:
-                    pass
-                except (NoRestV2Available, NotFoundException):
-                    rev_time = None
-                else:
-                    tmp = {r["revision"]: r["time"] for r in revisions}
-                    rev_time = tmp.get(rev)
+            if check_rev_time:
+                # Check the time in the associated remote if any
+                remote_name = layout.load_metadata().recipe.remote
+                remote = self._cache.registry.load_remotes()[remote_name] if remote_name else None
+                if remote:
+                    try:
+                        revisions = self._remote_manager.get_recipe_revisions(ref, remote)
+                    except RecipeNotFoundException:
+                        pass
+                    except (NoRestV2Available, NotFoundException):
+                        rev_time = None
+                    else:
+                        tmp = {r["revision"]: r["time"] for r in revisions}
+                        rev_time = tmp.get(rev)
 
             return [{"revision": rev, "time": rev_time}]
         else:
diff --git a/conans/test/functional/command/search_test.py b/conans/test/functional/command/search_test.py
index 5dcdb9940c5..fe63d41d8a9 100644
--- a/conans/test/functional/command/search_test.py
+++ b/conans/test/functional/command/search_test.py
@@ -1194,6 +1194,10 @@ class Test(ConanFile):
         client.run("search lib/1.0@user/testing --revisions")
         self.assertIn("bd761686d5c57b31f4cd85fd0329751f (No time)", client.out)
 
+        # test that the pattern search with --revisions enabled works
+        client.run("search li* --revisions")
+        self.assertIn("bd761686d5c57b31f4cd85fd0329751f (No time)", client.out)
+
         with patch.object(RevisionList, '_now', return_value=the_time):
             client.run("upload lib/1.0@user/testing -c")
 
@@ -1348,6 +1352,10 @@ class Test(ConanFile):
 
         # IN REMOTE
 
+        # Search with pattern and remotes
+        client.run("search * --revisions -r default", assert_error=True)
+        self.assertIn("ERROR: With --revision, specify a reference", client.out)
+
         # Search not found in remote
         client.run("search missing/1.0@conan/stable --revisions -r default", assert_error=True)
         self.assertIn("ERROR: Recipe not found: 'missing/1.0@conan/stable'", client.out)


Code review comments:
diff_hunk: @@ -1093,7 +1093,7 @@ def get_remote_by_name(self, remote_name):
         return self._cache.registry.load_remotes()[remote_name]
 
     @api_method
-    def get_recipe_revisions(self, reference, remote_name=None):
+    def get_recipe_revisions(self, reference, remote_name=None, check_rev_time=True):
path: conans/client/conan_api.py
text: @user1:
I think we shouldn't change this method. That `check_rev_time` is a patch.

diff_hunk: @@ -1212,17 +1212,36 @@ def search(self, *args):
                 else:
                     info = self._conan.get_package_revisions(repr(pref), remote_name=args.remote)
 
-                if not info:
-                    if not ref:
-                        msg = "With --revision, specify a reference (e.g {ref}) or a package " \
-                              "reference with " \
-                              "recipe revision (e.g {ref}#3453453453:d50a0d523d98c15bb147b18f" \
-                              "a7d203887c38be8b)".format(ref=_REFERENCE_EXAMPLE)
-                        raise ConanException(msg)
-                    info = self._conan.get_recipe_revisions(repr(ref),
-                                                            remote_name=args.remote)
-                self._outputer.print_revisions(ref, info, remote_name=args.remote)
-                return
+                if not ref and not info:
+                    exc_msg = "With --revision, specify a reference (e.g {ref}) a valid pattern " \
+                          "or a package reference with " \
+                          "recipe revision (e.g {ref}#3453453453:d50a0d523d98c15bb147b18f" \
+                          "a7d203887c38be8b)".format(ref=_REFERENCE_EXAMPLE)
+                    if args.remote:
+                        raise ConanException(exc_msg)
+                    else:
+                        info = self._conan.search_recipes(args.pattern_or_reference,
+                                                          remote_name=args.remote,
+                                                          case_sensitive=args.case_sensitive)
+                        if info["results"]:
path: conans/client/command.py
text: @user1:
This feels hacky too

diff_hunk: @@ -1194,6 +1194,10 @@ class Test(ConanFile):
         client.run("search lib/1.0@user/testing --revisions")
         self.assertIn("bd761686d5c57b31f4cd85fd0329751f (No time)", client.out)
 
+        # test that the pattern search with --revisions enabled works
+        client.run("search li* --revisions")
+        self.assertIn("bd761686d5c57b31f4cd85fd0329751f (No time)", client.out)
path: conans/test/functional/command/search_test.py
text: @user2:
Is it `lib/1.0@user1/testing#bd761686d5c57b31f4cd85fd0329751f (No time)`? In that case assert the whole line because it is confusing what is `bd761686d5c57b31f4cd85fd0329751f`