Repository: pypi/warehouse

Issue:
#3310 makes twine --skip-existing option useless
`twine` checks for message starting with `File already exists`:
https://github.com/pypa/twine/blob/bcb73e025102565c395e0bf322faebd7f72e76f7/twine/commands/upload.py#L58-L61
Message has been changed to one starting with `The filename or contents already exist` in #3310:
https://github.com/pypa/warehouse/blob/969695d030aef8b11d5b052e8731a80746c404c8/warehouse/forklift/legacy.py#L1083-L1094

c.f. pypa/twine#332
c.f. failing deployment: https://travis-ci.org/mayeut/gcovr/builds/360424325


Discussion:
@sigmavirus24 @jonparrott @di I'm offline much of today, could one of you take a look at this?

Thanks for the report, @mayeut.
@brainwane, given you're comment in https://github.com/pypa/twine/issues/332#issuecomment-377689258 saying it shall probably be fixed in `twine`, I guess one thing that could be done is add a comment in warehouse. Maybe something like:
`# changing the message might break twine --skip-existing`
This will prevent further issues from popping up. At least, there can be some coordination before an issue arises (e.g. release twine with new message support then change the message).

With my `twine` hat on, I've always been of the opinion that our current checking around this is fragile and sucks. If Warehouse can provide us with something better that would be fantastic. It seems like Warehouse, however, has traded one plain-text string for another. For this API, I understand returning plain-text/html, but I think if the client says they accept JSON, a JSON payload would be leagues better. Just my 2cents
Hmm, this is unfortunate. Seems like we should revert the error message change so that this behavior is restored for older Twine clients, and then work on improving the brittleness of this in Twine.

Resolving patch:
diff --git a/warehouse/forklift/legacy.py b/warehouse/forklift/legacy.py
--- a/warehouse/forklift/legacy.py
+++ b/warehouse/forklift/legacy.py
@@ -1097,11 +1097,16 @@ def file_upload(request):
             return Response()
         elif is_duplicate is not None:
             raise _exc_with_message(
-                HTTPBadRequest, "The filename or contents already exist. "
-                                "See " +
-                                request.route_url(
-                                    'help', _anchor='file-name-reuse'
-                                )
+                HTTPBadRequest,
+                # Note: Changing this error message to something that doesn't
+                # start with "File already exists" will break the
+                # --skip-existing functionality in twine
+                # ref: https://github.com/pypa/warehouse/issues/3482
+                # ref: https://github.com/pypa/twine/issues/332
+                "File already exists. See " +
+                request.route_url(
+                    'help', _anchor='file-name-reuse'
+                )
             )
 
         # Check to see if the file that was uploaded exists in our filename log


Test patch:
diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py
--- a/tests/unit/forklift/test_legacy.py
+++ b/tests/unit/forklift/test_legacy.py
@@ -1835,10 +1835,7 @@ def test_upload_fails_with_existing_filename_diff_content(self,
             pretend.call('help', _anchor='file-name-reuse')
         ]
         assert resp.status_code == 400
-        assert resp.status == (
-            "400 The filename or contents already exist. "
-            "See /the/help/url/"
-        )
+        assert resp.status == "400 File already exists. See /the/help/url/"
 
     def test_upload_fails_with_diff_filename_same_blake2(self,
                                                          pyramid_config,
@@ -1897,10 +1894,7 @@ def test_upload_fails_with_diff_filename_same_blake2(self,
             pretend.call('help', _anchor='file-name-reuse')
         ]
         assert resp.status_code == 400
-        assert resp.status == (
-            "400 The filename or contents already exist. "
-            "See /the/help/url/"
-        )
+        assert resp.status == "400 File already exists. See /the/help/url/"
 
     def test_upload_fails_with_wrong_filename(self, pyramid_config,
                                               db_request):