Repository: conan-io/conan
Pull request: Upload using pref as the recommended way
Changelog: Feature: The `conan upload` command can receive now the full package reference to upload a binary package. The `-p` argument is now deprecated.
Docs: https://github.com/conan-io/docs/pull/1300

Closes #5196

Problem statement:
Conan upload receiving a pref instead of -p and from file
- The same we did with `conan get`, the pattern could process automatically the package to be uploaded, the `-p` usage could be warned as deprecated.

- Additionally, following the syntax used by other programs like `gcc`, `conan upload @filepath` could read the reference from a file.

Hints from issue discussion:
> Additionally, following the syntax used by other programs like gcc, conan upload @filepath could read the reference from a file.

Probably not a good idea, we plan to be able to specify references like `@user/channel` to disambiguate.

Commit message: Added test

Patch to review:
diff --git a/conans/client/command.py b/conans/client/command.py
index a1085b3985d..236abb46f4a 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -83,6 +83,7 @@ def _fill_text(self, text, width, indent):
 _QUERY_EXAMPLE = ("os=Windows AND (arch=x86 OR compiler=gcc)")
 _PATTERN_EXAMPLE = ("boost/*")
 _REFERENCE_EXAMPLE = ("MyPackage/1.2@user/channel")
+_PREF_EXAMPLE = ("MyPackage/1.2@user/channel:af7901d8bdfde621d086181aa1c495c25a17b137")
 
 _BUILD_FOLDER_HELP = ("Directory for the build process. Defaulted to the current directory. A "
                       "relative path to current directory can also be specified")
@@ -91,7 +92,11 @@ def _fill_text(self, text, width, indent):
 _KEEP_SOURCE_HELP = ("Do not remove the source folder in local cache, even if the recipe changed. "
                      "Use this for testing purposes only")
 _PATTERN_OR_REFERENCE_HELP = ("Pattern or package recipe reference, e.g., '%s', "
-                              "'%s'" % (_REFERENCE_EXAMPLE, _PATTERN_EXAMPLE))
+                              "'%s'" % (_PATTERN_EXAMPLE, _REFERENCE_EXAMPLE))
+_PATTERN_REF_OR_PREF_HELP = ("Pattern, recipe reference or package reference e.g., '%s', "
+                              "'%s', '%s'" % (_PATTERN_EXAMPLE, _REFERENCE_EXAMPLE, _PREF_EXAMPLE))
+_PREF_OR_PREF_HELP = ("Recipe reference or package reference e.g., '%s', "
+                       "'%s'" % (_REFERENCE_EXAMPLE, _PREF_EXAMPLE))
 _PATH_HELP = ("Path to a folder containing a conanfile.py or to a recipe file "
               "e.g., my_folder/conanfile.py")
 _QUERY_HELP = ("Packages query: '%s'. The 'pattern_or_reference' parameter has "
@@ -1192,9 +1197,10 @@ def upload(self, *args):
         parser = argparse.ArgumentParser(description=self.upload.__doc__,
                                          prog="conan upload",
                                          formatter_class=SmartFormatter)
-        parser.add_argument('pattern_or_reference', help=_PATTERN_OR_REFERENCE_HELP)
-        parser.add_argument("-p", "--package", default=None, action=OnceArgument,
-                            help='package ID to upload')
+        parser.add_argument('pattern_or_reference', help=_PATTERN_REF_OR_PREF_HELP)
+        parser.add_argument("-p", "--package", default=None,
+                            help="Package ID [DEPRECATED: use full reference instead]",
+                            action=OnceArgument)
         parser.add_argument('-q', '--query', default=None, action=OnceArgument,
                             help="Only upload packages matching a specific query. " + _QUERY_HELP)
         parser.add_argument("-r", "--remote", action=OnceArgument,
@@ -1222,7 +1228,24 @@ def upload(self, *args):
 
         args = parser.parse_args(*args)
 
-        if args.query and args.package:
+        try:
+            pref = PackageReference.loads(args.pattern_or_reference, validate=True)
+            reference = pref.ref.full_repr()
+            package_id = pref.id
+        except ConanException:
+            reference = args.pattern_or_reference
+            package_id = args.package
+
+            if package_id:
+                self._user_io.out.warn("Usage of `--package` argument is deprecated."
+                                       " Use a full reference instead: "
+                                       "`conan upload [...] {}:{}`".format(reference, package_id))
+        else:
+            if args.package:
+                raise ConanException("Use a full package reference (preferred) or the `--package`"
+                                     " command argument, but not both.")
+
+        if args.query and package_id:
             raise ConanException("'-q' and '-p' parameters can't be used at the same time")
 
         cwd = os.getcwd()
@@ -1250,7 +1273,7 @@ def upload(self, *args):
             policy = None
 
         try:
-            info = self._conan.upload(pattern=args.pattern_or_reference, package=args.package,
+            info = self._conan.upload(pattern=reference, package=package_id,
                                       query=args.query, remote_name=args.remote,
                                       all_packages=args.all, policy=policy,
                                       confirm=args.confirm, retry=args.retry,
@@ -1453,7 +1476,7 @@ def get(self, *args):
         parser = argparse.ArgumentParser(description=self.get.__doc__,
                                          prog="conan get",
                                          formatter_class=SmartFormatter)
-        parser.add_argument('reference', help='package recipe reference')
+        parser.add_argument('reference', help=_PREF_OR_PREF_HELP)
         parser.add_argument('path',
                             help='Path to the file or directory. If not specified will get the '
                                  'conanfile if only a reference is specified and a conaninfo.txt '
diff --git a/conans/test/functional/command/upload_test.py b/conans/test/functional/command/upload_test.py
index cefe3a46fbf..c4addba9ab2 100644
--- a/conans/test/functional/command/upload_test.py
+++ b/conans/test/functional/command/upload_test.py
@@ -115,6 +115,38 @@ def non_existing_package_error_test(self):
         client.run("upload Pkg/0.1@user/channel -p hash1", assert_error=True)
         self.assertIn("ERROR: Recipe not found: 'Pkg/0.1@user/channel'", client.out)
 
+    def deprecated_p_arg_test(self):
+        client = self._client()
+        client.save({"conanfile.py": conanfile})
+        client.run("create . user/testing")
+        client.run("upload Hello0/1.2.1@user/testing -p {} -c".format(NO_SETTINGS_PACKAGE_ID))
+        self.assertIn("WARN: Usage of `--package` argument is deprecated. "
+                      "Use a full reference instead: `conan upload [...] "
+                      "Hello0/1.2.1@user/testing:{}`".format(NO_SETTINGS_PACKAGE_ID), client.out)
+
+    def upload_with_pref_test(self):
+        client = self._client()
+        client.save({"conanfile.py": conanfile})
+        client.run("create . user/testing")
+        client.run("upload Hello0/1.2.1@user/testing:{} -c".format(NO_SETTINGS_PACKAGE_ID))
+        self.assertNotIn("WARN: Usage of `--package` argument is deprecated. "
+                         "Use a full reference instead: `conan upload [...] "
+                         "Hello0/1.2.1@user/testing:{}`".format(NO_SETTINGS_PACKAGE_ID),
+                         client.out)
+        self.assertIn("Uploading package 1/1: {} to 'default'".format(NO_SETTINGS_PACKAGE_ID),
+                      client.out)
+
+    def upload_with_pref_and_p_test(self):
+        client = self._client()
+        client.save({"conanfile.py": conanfile})
+        client.run("create . user/testing")
+        client.run("upload Hello0/1.2.1@user/testing:{} -c -p {}".format(NO_SETTINGS_PACKAGE_ID,
+                                                                         NO_SETTINGS_PACKAGE_ID),
+                   assert_error=True)
+
+        self.assertIn("Use a full package reference (preferred) or the "
+                      "`--package` command argument, but not both.", client.out)
+
     def _client(self):
         if not hasattr(self, "_servers"):
             servers = {}


Code review comments:
diff_hunk: @@ -91,7 +92,11 @@ def _fill_text(self, text, width, indent):
 _KEEP_SOURCE_HELP = ("Do not remove the source folder in local cache, even if the recipe changed. "
                      "Use this for testing purposes only")
 _PATTERN_OR_REFERENCE_HELP = ("Pattern or package recipe reference, e.g., '%s', "
-                              "'%s'" % (_REFERENCE_EXAMPLE, _PATTERN_EXAMPLE))
+                              "'%s'" % (_PATTERN_EXAMPLE, _REFERENCE_EXAMPLE))
+_PATTERN_REF_OR_PREF_HELP = ("Pattern, recipe reference or package reference e.g., '%s', "
+                              "'%s', '%s'" % (_PATTERN_EXAMPLE, _REFERENCE_EXAMPLE, _PREF_EXAMPLE))
+_PREF_OR_PREF_HELP = ("Recipe reference or package reference e.g., '%s', "
path: conans/client/command.py
text: @user1:
```suggestion
_REF_OR_PREF_HELP = ("Recipe reference or package reference e.g., '%s', "
```

or `_REFERENCE_OR_PREF_HELP`