)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"814e98ce87f835c5cd858e80653b21a4771f7d39","unresolved":true,"context_lines":[{"line_number":4,"context_line":"Commit:     Alistair Coles \u003calistairncoles@gmail.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-03-23 15:49:59 +0000"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"WIP: versioning: fix version listing marker name"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"The marker should *always* be translated to the equivalent versions"},{"line_number":10,"context_line":"object name."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":2,"id":"ec0a6daf_8a60fdac","line":7,"range":{"start_line":7,"start_character":0,"end_line":7,"end_character":4},"updated":"2026-03-23 18:39:05.000000000","message":"stale","commit_id":"724697dbc4499191645274ef3f362005b4da8b1e"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"9f738f7d7e1a2a7935efecef539543ac2445f87c","unresolved":false,"context_lines":[{"line_number":4,"context_line":"Commit:     Alistair Coles \u003calistairncoles@gmail.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-03-23 15:49:59 +0000"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"WIP: versioning: fix version listing marker name"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"The marker should *always* be translated to the equivalent versions"},{"line_number":10,"context_line":"object name."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":2,"id":"9c7f6d18_63deefc1","line":7,"range":{"start_line":7,"start_character":0,"end_line":7,"end_character":4},"in_reply_to":"ec0a6daf_8a60fdac","updated":"2026-03-30 12:26:44.000000000","message":"Done","commit_id":"724697dbc4499191645274ef3f362005b4da8b1e"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":4,"context_line":"Commit:     Alistair Coles \u003calistairncoles@gmail.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-03-23 18:39:08 +0000"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"versioning: fix version listing marker name"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"The marker should *always* be translated to the equivalent versions"},{"line_number":10,"context_line":"object name."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":3,"id":"7273fc63_d3f67eaf","line":7,"updated":"2026-03-26 02:50:53.000000000","message":"would this be better to call out the `version-id-marker\u003dnull` more like the bug report?\n\ntruncated versions listing when version_marker is \u0027null\u0027\nhttps://bugs.launchpad.net/swift/+bug/2145423\n\nI don\u0027t actually know what *either* the \"version listing marker name\" OR the \"version-id-marker\" *is* - but I am sort of starting to develop this idea that if you make a listing request to swift w/ `version_marker\u003dnull` you\u0027re not going to get very interesting results back!","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"5535277e_9d2a94ba","updated":"2026-03-26 02:50:53.000000000","message":"I think this is an improvement to master and maybe sufficient to merge; I ran out of time for additional verification/test-writing of the \"version_id\u003dnull in the listing\" cases b/c I let myself get distracted by other things today.\n\nI\u0027d be a *little* happier if we had more targeted unittests:\n\n```\n(vagrant-swift-all-in-one) cgerrard@NVStation:~/Workspace/vagrant-swift-all-in-one/swift$ grep \"test_list\" test/unit/common/middleware/test_object_versioning.py | grep marker\n    def test_list_versions_marker_missing_marker(self):\n    def test_list_versions_marker(self):\n    def test_list_versions_delete_markers(self):\n```\n\nparticularly I think:\n\n```\ntest_list_versions_version_marker_null_no_version_id_null\ntest_list_versions_version_marker_null_with_version_id_null\n```\n\nwould be pretty great!  Possibly as follow up!  Since I\u0027m pretty sure this is AT LEAST correct for the `test_list_versions_version_marker_null_no_version_id_null` case already.  It\u0027d just be *nice* to have the \"XXX more work for more better\" test/patch in place before we merge this one just so we don\u0027t forget to write down what we\u0027ve figured out while it\u0027s all loaded in our heads ... bah, maybe it\u0027d be fine to merge this as \"obviously an improvement\" and worry about chasing down the rest of the bugs with `version_marker\u003dnull + version_id\u003dnull` when/if they come up.\n\nIt\u0027s late and I\u0027m tired, I\u0027ll try to check in again on this next week and see if I can push it any closer to over the line.","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"8d3bdc60_7109d1f4","updated":"2026-03-31 10:34:18.000000000","message":"I think this patch was only a partial fix. See https://bugs.launchpad.net/swift/+bug/2146885","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"8864523008cb73349d3ae0cd1d8a729b765f73f4","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"dd81c6f0_3ce14c7e","updated":"2026-03-30 18:25:57.000000000","message":"ok, i\u0027m convinced if we *need* to \"fix\" the `VersionIdMarker\u003dnull` for s3api this bug fix doesn\u0027t make it any \"harder\"\n\n982705: WIP: ?version_marker\u003dnull treat null as newest | https://review.opendev.org/c/openstack/swift/+/982705\n\nworst case is I guess we forget about it a long time and eventually decide that with this fix we\u0027ve *defined* the swift-api behavior of `version_marker\u003dnull` and can\u0027t change it; so object_versioning (and clients?) have to grow special `versions\u003dtrue\u0026s3_compatible\u003dtrue` support\n\nmaybe also sort of bad I guess would be that we ship this fix and s3api clients still aren\u0027t happy after `put-object-versioning status\u003dsuspended` (but is anyone really HAPPY toggling versioning on and off?)","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"}],"swift/common/middleware/versioned_writes/object_versioning.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"95f5ad87ef1cbf42dff6bb2f8e5fd3aec7ecb959","unresolved":true,"context_lines":[{"line_number":1118,"context_line":"        params \u003d req.params"},{"line_number":1119,"context_line":"        if \u0027marker\u0027 in params:"},{"line_number":1120,"context_line":"            params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1121,"context_line":"                params[\u0027marker\u0027]) + \u0027:\u0027  # just past all numbers"},{"line_number":1122,"context_line":""},{"line_number":1123,"context_line":"        if \u0027version_marker\u0027 in params:"},{"line_number":1124,"context_line":"            if \u0027marker\u0027 not in params:"}],"source_content_type":"text/x-python","patch_set":1,"id":"5e047230_417bd585","line":1121,"updated":"2026-03-20 16:02:43.000000000","message":"\u003e Previously version-id-marker\u003dnull would cause the version marker to be\nthe original object marker\n\nit would be good to have some tests - but I\u0027m a little confused about this fix (or the API)","commit_id":"d06cf74a36ea23e3645d5c3a51f9dc0caceebf50"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":false,"context_lines":[{"line_number":1118,"context_line":"        params \u003d req.params"},{"line_number":1119,"context_line":"        if \u0027marker\u0027 in params:"},{"line_number":1120,"context_line":"            params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1121,"context_line":"                params[\u0027marker\u0027]) + \u0027:\u0027  # just past all numbers"},{"line_number":1122,"context_line":""},{"line_number":1123,"context_line":"        if \u0027version_marker\u0027 in params:"},{"line_number":1124,"context_line":"            if \u0027marker\u0027 not in params:"}],"source_content_type":"text/x-python","patch_set":1,"id":"3361d4fb_be8c43c4","line":1121,"in_reply_to":"5e047230_417bd585","updated":"2026-03-26 02:50:53.000000000","message":"Done","commit_id":"d06cf74a36ea23e3645d5c3a51f9dc0caceebf50"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"95f5ad87ef1cbf42dff6bb2f8e5fd3aec7ecb959","unresolved":true,"context_lines":[{"line_number":1131,"context_line":"                    raise HTTPBadRequest(\u0027invalid version_marker param\u0027)"},{"line_number":1132,"context_line":""},{"line_number":1133,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_name("},{"line_number":1134,"context_line":"                    params[\u0027marker\u0027], ts.internal)"},{"line_number":1135,"context_line":""},{"line_number":1136,"context_line":"        delim \u003d params.get(\u0027delimiter\u0027, \u0027\u0027)"},{"line_number":1137,"context_line":"        # Exclude the set of chars used in version_id from user delimiters"}],"source_content_type":"text/x-python","patch_set":1,"id":"0199a6e9_df05b327","line":1134,"updated":"2026-03-20 16:02:43.000000000","message":"is it possible another fix would be to de-dent this?  Or to do so w/ `version_marker\u003dnull` ends up needing the `:` default anyway?","commit_id":"d06cf74a36ea23e3645d5c3a51f9dc0caceebf50"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"4e74ab90487925f674279381873da9d7479db0ea","unresolved":true,"context_lines":[{"line_number":1131,"context_line":"                    raise HTTPBadRequest(\u0027invalid version_marker param\u0027)"},{"line_number":1132,"context_line":""},{"line_number":1133,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_name("},{"line_number":1134,"context_line":"                    params[\u0027marker\u0027], ts.internal)"},{"line_number":1135,"context_line":""},{"line_number":1136,"context_line":"        delim \u003d params.get(\u0027delimiter\u0027, \u0027\u0027)"},{"line_number":1137,"context_line":"        # Exclude the set of chars used in version_id from user delimiters"}],"source_content_type":"text/x-python","patch_set":1,"id":"d5cde553_2777b9cb","line":1134,"in_reply_to":"0199a6e9_df05b327","updated":"2026-03-23 15:56:11.000000000","message":"\u003eis it possible another fix would be to de-dent this? \n\nthis statement uses ts so cannot be de-dented. But, my current change is broken anyway. Will fix.\n\n\u003e Or to do so w/ version_marker\u003dnull ends up needing the : default anyway?\n\nhmmm, now I am unsure what does \"version_marker\u003dnull\" even mean? There is no version in the versions container with name ``\\x00obj-name\\x00null``, but there is a concept in Swift object-versioning doc that \u0027null\u0027 refers to the *latest* variant that is now a version. But that\u0027s different to S3\u0027s concept of null which refers to the variant that existed prior to versioning being enabled (so often the oldest variant).\n\nAnyway, in the context of this middleware it probably makes sense to treat \u0027null\u0027 as sorting after any version id.\n\n```\nAssuming a list of:\n[a @ v1, a @ v2, b @ v1]\n\nthen:\n\nmarker\u003da -\u003e [b @ v1]\nmarker\u003da, version_marker\u003dv1 - \u003e [a @ v2, b @ v1]\nmarker\u003da, version_marker\u003dnull - \u003e [b @ v1]\n\n```","commit_id":"d06cf74a36ea23e3645d5c3a51f9dc0caceebf50"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[{"line_number":1131,"context_line":"                    raise HTTPBadRequest(\u0027invalid version_marker param\u0027)"},{"line_number":1132,"context_line":""},{"line_number":1133,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_name("},{"line_number":1134,"context_line":"                    params[\u0027marker\u0027], ts.internal)"},{"line_number":1135,"context_line":""},{"line_number":1136,"context_line":"        delim \u003d params.get(\u0027delimiter\u0027, \u0027\u0027)"},{"line_number":1137,"context_line":"        # Exclude the set of chars used in version_id from user delimiters"}],"source_content_type":"text/x-python","patch_set":1,"id":"abbfbf64_fcd06879","line":1134,"in_reply_to":"5298f31a_4eaa7c4c","updated":"2026-03-31 10:34:18.000000000","message":"Acknowledged","commit_id":"d06cf74a36ea23e3645d5c3a51f9dc0caceebf50"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1131,"context_line":"                    raise HTTPBadRequest(\u0027invalid version_marker param\u0027)"},{"line_number":1132,"context_line":""},{"line_number":1133,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_name("},{"line_number":1134,"context_line":"                    params[\u0027marker\u0027], ts.internal)"},{"line_number":1135,"context_line":""},{"line_number":1136,"context_line":"        delim \u003d params.get(\u0027delimiter\u0027, \u0027\u0027)"},{"line_number":1137,"context_line":"        # Exclude the set of chars used in version_id from user delimiters"}],"source_content_type":"text/x-python","patch_set":1,"id":"5298f31a_4eaa7c4c","line":1134,"in_reply_to":"d5cde553_2777b9cb","updated":"2026-03-26 02:50:53.000000000","message":"so when I do `\u0026versions\u003dblah\u0026marker\u003dtest\u0026version_marker\u003d1774481903.85073` I see a backend query in my logs like this:\n\n```\n\u003e\u003e\u003e unquote(\u0027/sdb4/780/AUTH_test/%00versions%00new-versions?prefix\u003d\u0026marker\u003d%00test%008225518096.14926\u0026limit\u003d\u0026delimiter\u003d\u0026reverse\u003d\u0026format\u003djson\u0026states\u003dlisting\u0027)\n\u0027/sdb4/780/AUTH_test/\\x00versions\\x00new-versions?prefix\u003d\u0026marker\u003d\\x00test\\x008225518096.14926\u0026limit\u003d\u0026delimiter\u003d\u0026reverse\u003d\u0026format\u003djson\u0026states\u003dlisting\u0027\n```\n\n... which makes sense, I want to continue listing versions of `test` picking up *after* the `version_id` from where I left off.\n\nWhen handling `version_marker\u003dnull`, by treating it \"as if it wasn\u0027t there\" we\u0027re essentially pretending \"if swift did return an item in a listing with `version_id: null` it would be at the END of the of the list of versions, and by saying you want to \"pickup after the `marker\u003dtest,version_marker\u003dnull` entry\" you\u0027re *essentially* asking for \"none of the versions of test including the last one which would have been null\"\n\nFWIW in my testing s3 sorts version_id\u003dnull BEFORE any versions, so it\u0027s a good thing we never return `version_id: null` - because w/ S3 I\u0027m pretty sure if a key doesn\u0027t HAVE a `VersionId: null` a query like `\u0027KeyMarker\u0027: \u0027test.big\u0027, \u0027VersionIdMarker\u0027: \u0027null\u0027` skips ALL versions of that key, not just the non-existent null version.","commit_id":"d06cf74a36ea23e3645d5c3a51f9dc0caceebf50"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"814e98ce87f835c5cd858e80653b21a4771f7d39","unresolved":true,"context_lines":[{"line_number":1115,"context_line":"        if not req.accept.best_match([\u0027application/json\u0027]):"},{"line_number":1116,"context_line":"            raise HTTPNotAcceptable(request\u003dreq)"},{"line_number":1117,"context_line":""},{"line_number":1118,"context_line":"        params \u003d dict(req.params)"},{"line_number":1119,"context_line":"        if \u0027marker\u0027 in params:"},{"line_number":1120,"context_line":"            if params.get(\u0027version_marker\u0027) in (\u0027null\u0027, None):"},{"line_number":1121,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("}],"source_content_type":"text/x-python","patch_set":2,"id":"cb1af21f_0ba5d8bd","line":1118,"range":{"start_line":1118,"start_character":17,"end_line":1118,"end_character":22},"updated":"2026-03-23 18:39:05.000000000","message":"I didn\u0027t find a low-hanging-fruit way to test this dict cloning because the unit tests wrap the middleware in a mini-pipeline. Plus, ``swob.Request.params`` is a little odd, it is not a direct proxy for the query string, which remains unchanged by modifications to the dict returned by ``req.params``. The ``req.params`` setter has to be used to actually change the query string.\n\nNevertheless it seems good hygiene to not be mutating the original ``req._params_cache``","commit_id":"724697dbc4499191645274ef3f362005b4da8b1e"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"9f738f7d7e1a2a7935efecef539543ac2445f87c","unresolved":false,"context_lines":[{"line_number":1115,"context_line":"        if not req.accept.best_match([\u0027application/json\u0027]):"},{"line_number":1116,"context_line":"            raise HTTPNotAcceptable(request\u003dreq)"},{"line_number":1117,"context_line":""},{"line_number":1118,"context_line":"        params \u003d dict(req.params)"},{"line_number":1119,"context_line":"        if \u0027marker\u0027 in params:"},{"line_number":1120,"context_line":"            if params.get(\u0027version_marker\u0027) in (\u0027null\u0027, None):"},{"line_number":1121,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("}],"source_content_type":"text/x-python","patch_set":2,"id":"20dbafe1_76c192ec","line":1118,"range":{"start_line":1118,"start_character":17,"end_line":1118,"end_character":22},"in_reply_to":"c0ddfc77_0de2eeb1","updated":"2026-03-30 12:26:44.000000000","message":"Done","commit_id":"724697dbc4499191645274ef3f362005b4da8b1e"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1115,"context_line":"        if not req.accept.best_match([\u0027application/json\u0027]):"},{"line_number":1116,"context_line":"            raise HTTPNotAcceptable(request\u003dreq)"},{"line_number":1117,"context_line":""},{"line_number":1118,"context_line":"        params \u003d dict(req.params)"},{"line_number":1119,"context_line":"        if \u0027marker\u0027 in params:"},{"line_number":1120,"context_line":"            if params.get(\u0027version_marker\u0027) in (\u0027null\u0027, None):"},{"line_number":1121,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("}],"source_content_type":"text/x-python","patch_set":2,"id":"c0ddfc77_0de2eeb1","line":1118,"range":{"start_line":1118,"start_character":17,"end_line":1118,"end_character":22},"in_reply_to":"cb1af21f_0ba5d8bd","updated":"2026-03-26 02:50:53.000000000","message":"eek, yeah the mutation looks odd now","commit_id":"724697dbc4499191645274ef3f362005b4da8b1e"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1120,"context_line":"            if \u0027marker\u0027 not in params:"},{"line_number":1121,"context_line":"                raise HTTPBadRequest(\u0027version_marker param requires marker\u0027)"},{"line_number":1122,"context_line":""},{"line_number":1123,"context_line":"            if params[\u0027version_marker\u0027] !\u003d \u0027null\u0027:"},{"line_number":1124,"context_line":"                try:"},{"line_number":1125,"context_line":"                    ts \u003d Timestamp(params.pop(\u0027version_marker\u0027))"},{"line_number":1126,"context_line":"                except ValueError:"}],"source_content_type":"text/x-python","patch_set":3,"id":"3c1a15d9_78082611","side":"PARENT","line":1123,"updated":"2026-03-26 02:50:53.000000000","message":"yeah the lack of an else block here is just WRONG, we let version_marker\u003dnull fall through and pass the original `params[marker]` from the user namespace query through to the version container unmodified!?  That\u0027s nuts!  THIS CHANGE IS BETTER!","commit_id":"e63f0b3d68770d4792e1606ff8fe924b3f25b7f9"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"9f738f7d7e1a2a7935efecef539543ac2445f87c","unresolved":false,"context_lines":[{"line_number":1120,"context_line":"            if \u0027marker\u0027 not in params:"},{"line_number":1121,"context_line":"                raise HTTPBadRequest(\u0027version_marker param requires marker\u0027)"},{"line_number":1122,"context_line":""},{"line_number":1123,"context_line":"            if params[\u0027version_marker\u0027] !\u003d \u0027null\u0027:"},{"line_number":1124,"context_line":"                try:"},{"line_number":1125,"context_line":"                    ts \u003d Timestamp(params.pop(\u0027version_marker\u0027))"},{"line_number":1126,"context_line":"                except ValueError:"}],"source_content_type":"text/x-python","patch_set":3,"id":"3448ecf3_c9cdabaf","side":"PARENT","line":1123,"in_reply_to":"3c1a15d9_78082611","updated":"2026-03-30 12:26:44.000000000","message":"Acknowledged","commit_id":"e63f0b3d68770d4792e1606ff8fe924b3f25b7f9"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":71,"context_line":""},{"line_number":72,"context_line":"    When object versioning is disabled on a container, new data will no longer"},{"line_number":73,"context_line":"    be versioned, but older versions remain untouched. Any new data ``PUT``"},{"line_number":74,"context_line":"    will result in a object with a ``null`` version-id. The versioning API can"},{"line_number":75,"context_line":"    be used to both list and operate on previous versions even while versioning"},{"line_number":76,"context_line":"    is disabled."},{"line_number":77,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"e3365383_8c7f890c","line":74,"updated":"2026-03-26 02:50:53.000000000","message":"wait, so this is saying that swift API *can* have objects with `version-id\u003dnull`???\n\nAre we sure \"treating version_marker\u003dnull as if it\u0027s not there\" is the correct thing to do when a listing DOES in fact have a `version_id\u003dnull` item in the list?","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":true,"context_lines":[{"line_number":71,"context_line":""},{"line_number":72,"context_line":"    When object versioning is disabled on a container, new data will no longer"},{"line_number":73,"context_line":"    be versioned, but older versions remain untouched. Any new data ``PUT``"},{"line_number":74,"context_line":"    will result in a object with a ``null`` version-id. The versioning API can"},{"line_number":75,"context_line":"    be used to both list and operate on previous versions even while versioning"},{"line_number":76,"context_line":"    is disabled."},{"line_number":77,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"0be0da8a_e27a183f","line":74,"in_reply_to":"40d90c5d_546c1c1b","updated":"2026-03-31 10:34:18.000000000","message":"Assuming your ``objA, 1`` is older than ``obj1, 2``, I see a different result: the versions are listed newest first.\n\nI (over)wrote objects a and b in a versioned container, then disabled versioning, then PUT a and b again to provoke null versions in the listing.\n\n```\nvagrant@saio:~/swift$ curl -s \"http://saio:8080/v1/AUTH_test/test?format\u003djson\u0026versions\u003dtrue\" -X GET -H \"X-Auth-Token: $OS_AUTH_TOKEN\" |jq \u0027.[] |[.name, .version_id]\u0027 -c\n[\"a\",\"null\"]\n[\"a\",\"1774950420.52684\"]\n[\"a\",\"1774950418.99672\"]\n[\"a\",\"1774950402.27600\"]\n[\"b\",\"null\"]\n[\"b\",\"1774950437.28465\"]\n```\n\n``?marker\u003da`` gets none of ``a`` and all of ``b``:\n\n```\nvagrant@saio:~/swift$ curl -s \"http://saio:8080/v1/AUTH_test/test?format\u003djson\u0026versions\u003dtrue\u0026marker\u003da\" -X GET -H \"X-Auth-Token: $OS_AUTH_TOKEN\" |jq \u0027.[] |[.name, .version_id]\u0027 -c\n[\"b\",\"null\"]\n[\"b\",\"1774950437.28465\"]\n```\n\n``?marker\u003da\u0026version_marker\u003d1774950418.99672`` gets *oldest* version of ``a`` plus all of ``b``:\n\n```\nvagrant@saio:~/swift$ curl -s \"http://saio:8080/v1/AUTH_test/test?format\u003djson\u0026versions\u003dtrue\u0026marker\u003da\u0026version_marker\u003d1774950418.99672\" -X GET -H \"X-Auth-Token: $OS_AUTH_TOKEN\" |jq \u0027.[] |[.name, .version_id]\u0027 -c\n[\"a\",\"1774950402.27600\"]\n[\"b\",\"null\"]\n[\"b\",\"1774950437.28465\"]\n```\n\n``?marker\u003da\u0026version_marker\u003dnull`` *before this patch* wrongly loses all non-null versions of ``a`` and ``b``:\n\n```\nvagrant@saio:~/swift$ curl -s \"http://saio:8080/v1/AUTH_test/test?format\u003djson\u0026versions\u003dtrue\u0026marker\u003da\u0026version_marker\u003dnull\" -X GET -H \"X-Auth-Token: $OS_AUTH_TOKEN\" |jq \u0027.[] |[.name, .version_id]\u0027 -c\n[\"b\",\"null\"]\n```\n\n``?marker\u003da\u0026version_marker\u003dnull`` *after this patch* loses all versions of ``a``:\n\n```\nvagrant@saio:~/swift$ curl -s \"http://saio:8080/v1/AUTH_test/test?format\u003djson\u0026versions\u003dtrue\u0026marker\u003da\u0026version_marker\u003dnull\" -X GET -H \"X-Auth-Token: $OS_AUTH_TOKEN\" |jq \u0027.[] |[.name, .version_id]\u0027 -c\n[\"b\",\"null\"]\n[\"b\",\"1774950437.28465\"]\n```\n\nBUT this is still wrong because all non-null versions of ``a`` are older than the null version of ``a`` and should therefore be included ?!? sigh...\n\nSo I think I only partially fixed the bug: with this patch we\u0027ve gone from \"if version_marker is null then no non-null versions of any object will be listed\" to \"if version_marker is null then no versions of the marker object will be listed (all non-null versions should be listed)\".\n\nThis means I cannot correctly paginate the listing if the first page happens to end on a null version of on object that also has non-null versions:\n\n```\nvagrant@saio:~/swift$ curl -s \"http://saio:8080/v1/AUTH_test/test?format\u003djson\u0026versions\u003dtrue\u0026limit\u003d5\" -X GET -H \"X-Auth-Token: $OS_AUTH_TOKEN\" |jq \u0027.[] |[.name, .version_id]\u0027 -c\n[\"a\",\"null\"]\n[\"a\",\"1774950420.52684\"]\n[\"a\",\"1774950418.99672\"]\n[\"a\",\"1774950402.27600\"]\n[\"b\",\"null\"]\n```\n\nuse the final entry of that list ^^^ as marker and version_marker for the next listing...\n\n```\nvagrant@saio:~/swift$ curl -s \"http://saio:8080/v1/AUTH_test/test?format\u003djson\u0026versions\u003dtrue\u0026marker\u003db\u0026version_marker\u003dnull\" -X GET -H \"X-Auth-Token: $OS_AUTH_TOKEN\" |jq \u0027.[] |[.name, .version_id]\u0027 -c\n```\n\noops, I should get \n``[\"b\",\"1774950437.28465\"]``\n\nThe impact of the remaining bug is limited: to have a null version in a listing (and therefore reasonably use it as a version_marker) requires either:\n\n- the object either existed before versioning was enabled and has not been overwritten, in which case there are no non-null versions to be lost in the listing 😊\n\nor\n\n- the object has versions, versioning has been disabled, and the object has been overwritten, in which case there *are* non-null versions that will go missing from the listing if version_marker\u003dnull is used :(\n\nI have reported this remaining bug here https://bugs.launchpad.net/swift/+bug/2146885","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"8864523008cb73349d3ae0cd1d8a729b765f73f4","unresolved":true,"context_lines":[{"line_number":71,"context_line":""},{"line_number":72,"context_line":"    When object versioning is disabled on a container, new data will no longer"},{"line_number":73,"context_line":"    be versioned, but older versions remain untouched. Any new data ``PUT``"},{"line_number":74,"context_line":"    will result in a object with a ``null`` version-id. The versioning API can"},{"line_number":75,"context_line":"    be used to both list and operate on previous versions even while versioning"},{"line_number":76,"context_line":"    is disabled."},{"line_number":77,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"40d90c5d_546c1c1b","line":74,"in_reply_to":"74a82eb1_e9935a14","updated":"2026-03-30 18:25:57.000000000","message":"\u003e The null version is always the latest version\n\u003e is equivalent to list everything after that marker\n\nummm, no I don\u0027t think so?  given:\n\n```\nobjA, null\nobjA, 1\nobjA, 2\nobjB, null\n```\n\n`?marker\u003dobjA`\n\n```\nobjB, null\n```\n\n`?marker\u003dobjA,version_marker\u003d1`\n\n```\nobjA, 2\nobjB, null\n```\n\n`?marker\u003dobjA,version_marker\u003dnull`\n\ncurrent implementation:\n\n```\nobjB, null\n```\n\n\"better\" (?) implementation:\n\n```\nobjA, 1\nobjA, 2\nobjB, null\n```\n\nI *think* that might be more consistent with AWS S3 `VersionIdMarker\u003dnull` handling according to my testing:\n\nhttps://bugs.launchpad.net/swift/+bug/2145423/comments/3","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"9f738f7d7e1a2a7935efecef539543ac2445f87c","unresolved":true,"context_lines":[{"line_number":71,"context_line":""},{"line_number":72,"context_line":"    When object versioning is disabled on a container, new data will no longer"},{"line_number":73,"context_line":"    be versioned, but older versions remain untouched. Any new data ``PUT``"},{"line_number":74,"context_line":"    will result in a object with a ``null`` version-id. The versioning API can"},{"line_number":75,"context_line":"    be used to both list and operate on previous versions even while versioning"},{"line_number":76,"context_line":"    is disabled."},{"line_number":77,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"74a82eb1_e9935a14","line":74,"in_reply_to":"e3365383_8c7f890c","updated":"2026-03-30 12:26:44.000000000","message":"I\u0027m not quite sure we are \"treating version_marker\u003dnull as if it\u0027s not there\", it\u0027s just that the outcome is the same because of the semantics of marker and version_marker.\n\n1. If given only marker, list everything after that marker.\n\n2. If given marker and version_marker, list everything after that version of that marker (so the version_marker actually winds the start of the list back from \"after marker\").\n\n3. If given marker and version_marker\u003dnull, list everything after the null version of that marker. The null version is always the latest version so this is equivalent to list everything after that marker i.e. the same as 1.","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1117,"context_line":""},{"line_number":1118,"context_line":"        params \u003d dict(req.params)"},{"line_number":1119,"context_line":"        if \u0027marker\u0027 in params:"},{"line_number":1120,"context_line":"            if params.get(\u0027version_marker\u0027) in (\u0027null\u0027, None):"},{"line_number":1121,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1122,"context_line":"                    params[\u0027marker\u0027]) + \u0027:\u0027  # just past all numbers"},{"line_number":1123,"context_line":"            else:"}],"source_content_type":"text/x-python","patch_set":3,"id":"63cf8144_40b99ae1","line":1120,"updated":"2026-03-26 02:50:53.000000000","message":"oh, ok - so `version_marker\u003dnull` is *exactly* the same as not sending `version_marker` - cool!","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[{"line_number":1117,"context_line":""},{"line_number":1118,"context_line":"        params \u003d dict(req.params)"},{"line_number":1119,"context_line":"        if \u0027marker\u0027 in params:"},{"line_number":1120,"context_line":"            if params.get(\u0027version_marker\u0027) in (\u0027null\u0027, None):"},{"line_number":1121,"context_line":"                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix("},{"line_number":1122,"context_line":"                    params[\u0027marker\u0027]) + \u0027:\u0027  # just past all numbers"},{"line_number":1123,"context_line":"            else:"}],"source_content_type":"text/x-python","patch_set":3,"id":"f8da4d0a_ed1527cc","line":1120,"in_reply_to":"63cf8144_40b99ae1","updated":"2026-03-31 10:34:18.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1176,"context_line":"        else:"},{"line_number":1177,"context_line":"            versions_req.params \u003d {"},{"line_number":1178,"context_line":"                k: params.get(k, \u0027\u0027) for k in ("},{"line_number":1179,"context_line":"                    \u0027prefix\u0027, \u0027marker\u0027, \u0027limit\u0027, \u0027delimiter\u0027, \u0027reverse\u0027)}"},{"line_number":1180,"context_line":"            versions_resp \u003d versions_req.get_response(self.app)"},{"line_number":1181,"context_line":""},{"line_number":1182,"context_line":"        if versions_resp is None \\"}],"source_content_type":"text/x-python","patch_set":3,"id":"9f61f7cf_34da7086","line":1179,"updated":"2026-03-26 02:50:53.000000000","message":"I wonder why we felt it was so necessary to mutate/pop the original `req.params` if we\u0027re just going to do a filtered copy anyway?  Creating a fresh dict with just the keys we want seems so much better!?","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[{"line_number":1176,"context_line":"        else:"},{"line_number":1177,"context_line":"            versions_req.params \u003d {"},{"line_number":1178,"context_line":"                k: params.get(k, \u0027\u0027) for k in ("},{"line_number":1179,"context_line":"                    \u0027prefix\u0027, \u0027marker\u0027, \u0027limit\u0027, \u0027delimiter\u0027, \u0027reverse\u0027)}"},{"line_number":1180,"context_line":"            versions_resp \u003d versions_req.get_response(self.app)"},{"line_number":1181,"context_line":""},{"line_number":1182,"context_line":"        if versions_resp is None \\"}],"source_content_type":"text/x-python","patch_set":3,"id":"f2996b3e_287fee7f","line":1179,"in_reply_to":"9f61f7cf_34da7086","updated":"2026-03-31 10:34:18.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1269,"context_line":"                limit \u003d constrain_req_limit(req, CONTAINER_LISTING_LIMIT)"},{"line_number":1270,"context_line":"                body \u003d build_listing("},{"line_number":1271,"context_line":"                    null_listing, versions_listing,"},{"line_number":1272,"context_line":"                    subdir_listing, broken_listing,"},{"line_number":1273,"context_line":"                    reverse\u003dconfig_true_value(params.get(\u0027reverse\u0027, \u0027no\u0027)),"},{"line_number":1274,"context_line":"                    limit\u003dlimit,"},{"line_number":1275,"context_line":"                )"}],"source_content_type":"text/x-python","patch_set":3,"id":"e8696477_7cb12c81","line":1272,"updated":"2026-03-26 02:50:53.000000000","message":"I\u0027m not sure what `broken_listing` is exactly, but the fact we don\u0027t pass in `current_versions` or `primary_listing` to this method I *think* has something to do why making a user-namespace marker query into the *versions-reserved-namespace* is SO wrong and results in an empty listing response.\n\nIf we go looking up `marker\u003dtest` in a `%00versions%00test` test container filled with objects named `%00test%00...` and get back an empty `versions_listing`... yeah, we\u0027re going to have a bad time.  This has got to be an improvement.","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"9f738f7d7e1a2a7935efecef539543ac2445f87c","unresolved":false,"context_lines":[{"line_number":1269,"context_line":"                limit \u003d constrain_req_limit(req, CONTAINER_LISTING_LIMIT)"},{"line_number":1270,"context_line":"                body \u003d build_listing("},{"line_number":1271,"context_line":"                    null_listing, versions_listing,"},{"line_number":1272,"context_line":"                    subdir_listing, broken_listing,"},{"line_number":1273,"context_line":"                    reverse\u003dconfig_true_value(params.get(\u0027reverse\u0027, \u0027no\u0027)),"},{"line_number":1274,"context_line":"                    limit\u003dlimit,"},{"line_number":1275,"context_line":"                )"}],"source_content_type":"text/x-python","patch_set":3,"id":"d9f43be8_1516cba5","line":1272,"in_reply_to":"e8696477_7cb12c81","updated":"2026-03-30 12:26:44.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"}],"test/functional/test_object_versioning.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1645,"context_line":""},{"line_number":1646,"context_line":"    def test_list_marker_and_version_marker_is_null(self):"},{"line_number":1647,"context_line":"        obj1_v1, obj1_v2, obj1_v3, obj1_v4, obj2_v1, obj3_v1 \u003d \\"},{"line_number":1648,"context_line":"            self._prep_object_versions()"},{"line_number":1649,"context_line":""},{"line_number":1650,"context_line":"        # list all versions in container"},{"line_number":1651,"context_line":"        listing_parms \u003d {\u0027format\u0027: \u0027json\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"53aa8d81_3d25418e","line":1648,"updated":"2026-03-26 02:50:53.000000000","message":"```\nobj1_v1[\u0027name\u0027] \u003d \u0027c\u0027 + Utils.create_name()\n```\n\nok, so the order of these objects matters and is predicible.\n\n\n```\n(Pdb) !obj1_v1\n{\u0027name\u0027: \u0027ce495937b693e4a618800ac8ae624da2b\u0027, \u0027id\u0027: \u00271774480924.57430\u0027}\n(Pdb) !obj1_v2\n{\u0027name\u0027: \u0027ce495937b693e4a618800ac8ae624da2b\u0027, \u0027id\u0027: \u00271774480924.62471\u0027}\n(Pdb) !obj2_v2\n*** NameError: name \u0027obj2_v2\u0027 is not defined\n(Pdb) !obj2_v1\n{\u0027name\u0027: \u0027b0f8c1bc11ee340d689459430243ed684\u0027, \u0027id\u0027: \u00271774480924.79392\u0027}\n(Pdb) !obj3_v1\n{\u0027name\u0027: \u0027a81f8cf7142584e639c520287d5e79c5d\u0027, \u0027id\u0027: \u00271774480924.84489\u0027}\n```","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"9f738f7d7e1a2a7935efecef539543ac2445f87c","unresolved":false,"context_lines":[{"line_number":1645,"context_line":""},{"line_number":1646,"context_line":"    def test_list_marker_and_version_marker_is_null(self):"},{"line_number":1647,"context_line":"        obj1_v1, obj1_v2, obj1_v3, obj1_v4, obj2_v1, obj3_v1 \u003d \\"},{"line_number":1648,"context_line":"            self._prep_object_versions()"},{"line_number":1649,"context_line":""},{"line_number":1650,"context_line":"        # list all versions in container"},{"line_number":1651,"context_line":"        listing_parms \u003d {\u0027format\u0027: \u0027json\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"9f75cf5b_f68b247b","line":1648,"in_reply_to":"53aa8d81_3d25418e","updated":"2026-03-30 12:26:44.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1651,"context_line":"        listing_parms \u003d {\u0027format\u0027: \u0027json\u0027,"},{"line_number":1652,"context_line":"                         \u0027marker\u0027: obj2_v1[\u0027name\u0027],"},{"line_number":1653,"context_line":"                         \u0027version_marker\u0027: \u0027null\u0027,"},{"line_number":1654,"context_line":"                         \u0027versions\u0027: None}"},{"line_number":1655,"context_line":"        prev_versions \u003d self.env.container.files(parms\u003dlisting_parms)"},{"line_number":1656,"context_line":"        for pv in prev_versions:"},{"line_number":1657,"context_line":"            pv.pop(\u0027last_modified\u0027)"}],"source_content_type":"text/x-python","patch_set":3,"id":"2351de4d_48978c47","line":1654,"updated":"2026-03-26 02:50:53.000000000","message":"I\u0027m not sure this is best minimal set of objects to demonstrate the issue exactly - that or maybe it doesn\u0027t matter because `version_marker\u003dnull` is so terribly undertested and broken almost anything will do!\n\n```\nvagrant@saio:~$ curl -s -H \"x-auth-token: $OS_AUTH_TOKEN\" \"${OS_STORAGE_URL}/35e30ce987-objs?format\u003djson\u0026versions\u003dblah\" | jq \u0027.[] | with_entries(select([.key] | inside([\"name\", \"content_type\", \"is_latest\"])))\u0027\n{\n  \"name\": \"a81f8cf7142584e639c520287d5e79c5d\",\n  \"content_type\": \"application/x-deleted;swift_versions_deleted\u003d1\",\n  \"is_latest\": true\n}\n{\n  \"name\": \"b0f8c1bc11ee340d689459430243ed684\",\n  \"content_type\": \"text/jibberish20\",\n  \"is_latest\": true\n}\n{\n  \"name\": \"ce495937b693e4a618800ac8ae624da2b\",\n  \"content_type\": \"application/x-deleted;swift_versions_deleted\u003d1\",\n  \"is_latest\": true\n}\n{\n  \"name\": \"ce495937b693e4a618800ac8ae624da2b\",\n  \"content_type\": \"text/jibberish13\",\n  \"is_latest\": false\n}\n{\n  \"name\": \"ce495937b693e4a618800ac8ae624da2b\",\n  \"content_type\": \"text/jibberish12\",\n  \"is_latest\": false\n}\n{\n  \"name\": \"ce495937b693e4a618800ac8ae624da2b\",\n  \"content_type\": \"text/jibberish11\",\n  \"is_latest\": false\n}\nvagrant@saio:~$ curl -s -H \"x-auth-token: $OS_AUTH_TOKEN\" \"${OS_STORAGE_URL}/35e30ce987-objs?format\u003djson\" | jq \u0027.[] | with_entries(select([.key] | inside([\"name\", \"content_type\", \"is_latest\"])))\u0027\n{\n  \"name\": \"b0f8c1bc11ee340d689459430243ed684\",\n  \"content_type\": \"text/jibberish20\"\n}\n```\n\n^ none of those have a \"version_id\u003dnull\" - what does a `version_marker\u003dnull` query *mean* exactly?","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"9f738f7d7e1a2a7935efecef539543ac2445f87c","unresolved":true,"context_lines":[{"line_number":1651,"context_line":"        listing_parms \u003d {\u0027format\u0027: \u0027json\u0027,"},{"line_number":1652,"context_line":"                         \u0027marker\u0027: obj2_v1[\u0027name\u0027],"},{"line_number":1653,"context_line":"                         \u0027version_marker\u0027: \u0027null\u0027,"},{"line_number":1654,"context_line":"                         \u0027versions\u0027: None}"},{"line_number":1655,"context_line":"        prev_versions \u003d self.env.container.files(parms\u003dlisting_parms)"},{"line_number":1656,"context_line":"        for pv in prev_versions:"},{"line_number":1657,"context_line":"            pv.pop(\u0027last_modified\u0027)"}],"source_content_type":"text/x-python","patch_set":3,"id":"61108d63_c9efda00","line":1654,"in_reply_to":"2351de4d_48978c47","updated":"2026-03-30 12:26:44.000000000","message":"\u003e what does a version_marker\u003dnull query mean exactly\nit means list everything after the null version of the marker object. \n\nThe null version is given to the latest variant of an object that has not been versioned, either because the object has not yet been overwritten while versioning is enabled, or because versioning has been disabled and the object has then been overwritten.\n\nIf a null version does exist then it is the latest variant of an object, so it seems reasonable that if version_marker\u003dnull then it should mean \u0027list everything after the null version\u0027. This is equivalent to not specifying version_marker at all.","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":true,"context_lines":[{"line_number":1651,"context_line":"        listing_parms \u003d {\u0027format\u0027: \u0027json\u0027,"},{"line_number":1652,"context_line":"                         \u0027marker\u0027: obj2_v1[\u0027name\u0027],"},{"line_number":1653,"context_line":"                         \u0027version_marker\u0027: \u0027null\u0027,"},{"line_number":1654,"context_line":"                         \u0027versions\u0027: None}"},{"line_number":1655,"context_line":"        prev_versions \u003d self.env.container.files(parms\u003dlisting_parms)"},{"line_number":1656,"context_line":"        for pv in prev_versions:"},{"line_number":1657,"context_line":"            pv.pop(\u0027last_modified\u0027)"}],"source_content_type":"text/x-python","patch_set":3,"id":"6f1d060d_275f6e39","line":1654,"in_reply_to":"61108d63_c9efda00","updated":"2026-03-31 10:34:18.000000000","message":"\u003eIf a null version does exist then it is the latest variant of an object, so it seems reasonable that if version_marker\u003dnull then it should mean \u0027list everything after the null version\u0027. This is equivalent to not specifying version_marker at all.\n\nUPDATE: I lost sight of the versions listing being in reverse version order, so if version_marker\u003dnull, meaning \u0027list everything *that sorts* after the null version\u0027, then this should include all non-null versions of the marker object. This is NOT equivalent to not specifying version_marker at all.","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":1683,"context_line":"            \u0027hash\u0027: md5(b\u0027version1\u0027, usedforsecurity\u003dFalse).hexdigest(),"},{"line_number":1684,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1685,"context_line":"            \u0027version_id\u0027: obj1_v1[\u0027id\u0027],"},{"line_number":1686,"context_line":"        }])"},{"line_number":1687,"context_line":""},{"line_number":1688,"context_line":"    def test_list_marker_and_version_marker(self):"},{"line_number":1689,"context_line":"        obj1_v1, obj1_v2, obj1_v3, obj1_v4, obj2_v1, obj3_v1 \u003d \\"}],"source_content_type":"text/x-python","patch_set":3,"id":"9f7f42ec_94a6d29b","line":1686,"updated":"2026-03-26 02:50:53.000000000","message":"with change reverted this fails:\n\n```\nE       AssertionError: Lists differ: [] !\u003d [{\u0027name\u0027: \u0027cc2fb83836dc14280b339cf3cab7568[754 chars]04\u0027}]\nE       \nE       Second list contains 4 additional elements.\nE       First extra element 0:\nE       {\u0027name\u0027: \u0027cc2fb83836dc14280b339cf3cab7568ea\u0027, \u0027bytes\u0027: 0, \u0027content_type\u0027: \u0027application/x-deleted;swift_versions_deleted\u003d1\u0027, \u0027hash\u0027: \u0027d41d8cd98f00b204e9800998ecf8427e\u0027, \u0027is_latest\u0027: True, \u0027version_id\u0027: \u00271774480893.65858\u0027}\nE       \nE       Diff is 898 characters long. Set self.maxDiff to None to see it.\n\nswift/test/functional/test_object_versioning.py:1658: AssertionError\n```\n\nswift is returning *nothing*!?\n\n```\n(Pdb) !prev_versions\n[]\n```\n\n```\n(Pdb) obj2_v1[\u0027name\u0027]\n\u0027b193f44d7ac5e499fad1191578b9b5830\u0027\nvagrant@saio:~$ swift list a9ccbef6a9-objs --versions\n           0 2026-03-26 02:03:43 1774490623.44552 application/x-deleted;swift_versions_deleted\u003d1 af83625522d76412eacbc3d710dcedd0d\n           8 2026-03-26 02:03:43 1774490623.39624         text/jibberish20 b193f44d7ac5e499fad1191578b9b5830\n           0 2026-03-26 02:03:43 1774490623.35139 application/x-deleted;swift_versions_deleted\u003d1 cd5968fc796d8440490f3e315dbb1b66e\n           8 2026-03-26 02:03:43 1774490623.28042         text/jibberish13 cd5968fc796d8440490f3e315dbb1b66e\n           8 2026-03-26 02:03:43 1774490623.22734         text/jibberish12 cd5968fc796d8440490f3e315dbb1b66e\n           8 2026-03-26 02:03:43 1774490623.17951         text/jibberish11 cd5968fc796d8440490f3e315dbb1b66e\n```\n\nso w/ `marker\u003db193f44d7ac5e499fad1191578b9b5830\u0026version_marker\u003dnull`\n\nI\u0027d *expect* everything that starts with a `c`\n\nbefore:\n\n```\nvagrant@saio:~$ curl -s -H \"x-auth-token: $OS_AUTH_TOKEN\" \"${OS_STORAGE_URL}/a9ccbef6a9-objs?format\u003djson\u0026versions\u003dblah\u0026marker\u003db193f44d7ac5e499fad1191578b9b5830\" | jq \u0027.[] | with_entries(select([.key] | inside([\"name\", \"content_type\", \"is_latest\", \"version_id\"])))\u0027\n{\n  \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n  \"content_type\": \"application/x-deleted;swift_versions_deleted\u003d1\",\n  \"is_latest\": true,\n  \"version_id\": \"1774490623.35139\"\n}\n{\n  \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n  \"content_type\": \"text/jibberish13\",\n  \"is_latest\": false,\n  \"version_id\": \"1774490623.28042\"\n}\n{\n  \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n  \"content_type\": \"text/jibberish12\",\n  \"is_latest\": false,\n  \"version_id\": \"1774490623.22734\"\n}\n{\n  \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n  \"content_type\": \"text/jibberish11\",\n  \"is_latest\": false,\n  \"version_id\": \"1774490623.17951\"\n}\nvagrant@saio:~$ curl -s -H \"x-auth-token: $OS_AUTH_TOKEN\" \"${OS_STORAGE_URL}/a9ccbef6a9-objs?format\u003djson\u0026versions\u003dblah\u0026marker\u003db193f44d7ac5e499fad1191578b9b5830\u0026version_marker\u003dnull\" | jq .\n[]\n```\n\nafter:\n\n```\nvagrant@saio:~$ curl -s -H \"x-auth-token: $OS_AUTH_TOKEN\" \"${OS_STORAGE_URL}/a9ccbef6a9-objs?format\u003djson\u0026versions\u003dblah\u0026marker\u003db193f44d7ac5e499fad1191578b9b5830\" | jq \u0027.[] | with_entries(select([.key] | inside([\"name\", \"content_type\", \"is_latest\", \"version_id\"])))\u0027\n{\n  \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n  \"content_type\": \"application/x-deleted;swift_versions_deleted\u003d1\",\n  \"is_latest\": true,\n  \"version_id\": \"1774490623.35139\"\n}\n{\n  \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n  \"content_type\": \"text/jibberish13\",\n  \"is_latest\": false,\n  \"version_id\": \"1774490623.28042\"\n}\n{\n  \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n  \"content_type\": \"text/jibberish12\",\n  \"is_latest\": false,\n  \"version_id\": \"1774490623.22734\"\n}\n{\n  \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n  \"content_type\": \"text/jibberish11\",\n  \"is_latest\": false,\n  \"version_id\": \"1774490623.17951\"\n}\nvagrant@saio:~$ curl -s -H \"x-auth-token: $OS_AUTH_TOKEN\" \"${OS_STORAGE_URL}/a9ccbef6a9-objs?format\u003djson\u0026versions\u003dblah\u0026marker\u003db193f44d7ac5e499fad1191578b9b5830\u0026version_marker\u003dnull\" | jq .\n[\n  {\n    \"bytes\": 0,\n    \"hash\": \"d41d8cd98f00b204e9800998ecf8427e\",\n    \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n    \"content_type\": \"application/x-deleted;swift_versions_deleted\u003d1\",\n    \"last_modified\": \"2026-03-26T02:03:43.351390\",\n    \"is_latest\": true,\n    \"version_id\": \"1774490623.35139\"\n  },\n  {\n    \"bytes\": 8,\n    \"hash\": \"1e2340e7f1d4ec2c23448854649f1ded\",\n    \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n    \"content_type\": \"text/jibberish13\",\n    \"last_modified\": \"2026-03-26T02:03:43.280420\",\n    \"is_latest\": false,\n    \"version_id\": \"1774490623.28042\"\n  },\n  {\n    \"bytes\": 8,\n    \"hash\": \"2e0e95285f08a07dea17e7ee111b21c8\",\n    \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n    \"content_type\": \"text/jibberish12\",\n    \"last_modified\": \"2026-03-26T02:03:43.227340\",\n    \"is_latest\": false,\n    \"version_id\": \"1774490623.22734\"\n  },\n  {\n    \"bytes\": 8,\n    \"hash\": \"966634ebf2fc135707d6753692bf4b1e\",\n    \"name\": \"cd5968fc796d8440490f3e315dbb1b66e\",\n    \"content_type\": \"text/jibberish11\",\n    \"last_modified\": \"2026-03-26T02:03:43.179510\",\n    \"is_latest\": false,\n    \"version_id\": \"1774490623.17951\"\n  }\n]\n```\n\n^ SEEMS BETTER!!!","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[{"line_number":1683,"context_line":"            \u0027hash\u0027: md5(b\u0027version1\u0027, usedforsecurity\u003dFalse).hexdigest(),"},{"line_number":1684,"context_line":"            \u0027is_latest\u0027: False,"},{"line_number":1685,"context_line":"            \u0027version_id\u0027: obj1_v1[\u0027id\u0027],"},{"line_number":1686,"context_line":"        }])"},{"line_number":1687,"context_line":""},{"line_number":1688,"context_line":"    def test_list_marker_and_version_marker(self):"},{"line_number":1689,"context_line":"        obj1_v1, obj1_v2, obj1_v3, obj1_v4, obj2_v1, obj3_v1 \u003d \\"}],"source_content_type":"text/x-python","patch_set":3,"id":"016c21e7_93ac9b16","line":1686,"in_reply_to":"9f7f42ec_94a6d29b","updated":"2026-03-31 10:34:18.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"}],"test/unit/common/middleware/test_object_versioning.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":2711,"context_line":"        self.assertEqual(status, \u0027400 Bad Request\u0027)"},{"line_number":2712,"context_line":"        self.assertEqual(body, b\u0027invalid version_marker param\u0027)"},{"line_number":2713,"context_line":""},{"line_number":2714,"context_line":"    def test_list_versions_marker(self):"},{"line_number":2715,"context_line":"        listing_body \u003d [{"},{"line_number":2716,"context_line":"            \u0027bytes\u0027: 8,"},{"line_number":2717,"context_line":"            \u0027name\u0027: \u0027non-versioned-obj\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"efe0173e_2953fc08","line":2714,"updated":"2026-03-26 02:50:53.000000000","message":"do be honest I think I\u0027d be a little happier if we had from ver","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":2812,"context_line":"              \u0027marker\u0027: \u0027\\x00obj\\x00:\u0027,"},{"line_number":2813,"context_line":"              \u0027prefix\u0027: \u0027\u0027,"},{"line_number":2814,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2815,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2816,"context_line":""},{"line_number":2817,"context_line":"        # marker and version_marker\u003dnull"},{"line_number":2818,"context_line":"        self.app.clear_calls()"}],"source_content_type":"text/x-python","patch_set":3,"id":"89981260_9f3793e1","line":2815,"updated":"2026-03-26 02:50:53.000000000","message":"with the change reverted I see the correct marker for queries that don\u0027t send `version_marker\u003dnull`:\n\n```\n(Pdb) ![c.req.params.get(\u0027marker\u0027) for c in self.app.call_list]\n[\u0027obj\u0027, \u0027\\x00obj\\x00:\u0027]\n```","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[{"line_number":2812,"context_line":"              \u0027marker\u0027: \u0027\\x00obj\\x00:\u0027,"},{"line_number":2813,"context_line":"              \u0027prefix\u0027: \u0027\u0027,"},{"line_number":2814,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2815,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2816,"context_line":""},{"line_number":2817,"context_line":"        # marker and version_marker\u003dnull"},{"line_number":2818,"context_line":"        self.app.clear_calls()"}],"source_content_type":"text/x-python","patch_set":3,"id":"d87df076_b189f9c5","line":2815,"in_reply_to":"89981260_9f3793e1","updated":"2026-03-31 10:34:18.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":2814,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2815,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2816,"context_line":""},{"line_number":2817,"context_line":"        # marker and version_marker\u003dnull"},{"line_number":2818,"context_line":"        self.app.clear_calls()"},{"line_number":2819,"context_line":"        req \u003d Request.blank("},{"line_number":2820,"context_line":"            \u0027/v1/a/c?versions\u0026marker\u003dobj\u0026version_marker\u003dnull\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"cb9633a0_bda51513","line":2817,"updated":"2026-03-26 02:50:53.000000000","message":"wow literally NO existing tests for listings w/ `version_marker\u003dnull`???\n\n```\n(vagrant-swift-all-in-one) cgerrard@NVStation:~/Workspace/vagrant-swift-all-in-one/swift$ grep \"version_marker\" test/unit/common/middleware/test_object_versioning.py -n\n2740:            \u0027/v1/a/c?versions\u0026version_marker\u003d1\u0027,\n2745:        self.assertEqual(body, b\u0027version_marker param requires marker\u0027)\n2748:            \u0027/v1/a/c?versions\u0026marker\u003dobj\u0026version_marker\u003did\u0027,\n2753:        self.assertEqual(body, b\u0027invalid version_marker param\u0027)\n2846:        # version_marker\n2860:            \u0027/v1/a/c?versions\u0026marker\u003dobj\u0026version_marker\u003d0000000010.00000\u0027,\n2870:        # version_marker with hex_part\n2881:            \u0027\u0026version_marker\u003d0000000010.00000_2edcba9876123456\u0027,\n```","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[{"line_number":2814,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2815,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2816,"context_line":""},{"line_number":2817,"context_line":"        # marker and version_marker\u003dnull"},{"line_number":2818,"context_line":"        self.app.clear_calls()"},{"line_number":2819,"context_line":"        req \u003d Request.blank("},{"line_number":2820,"context_line":"            \u0027/v1/a/c?versions\u0026marker\u003dobj\u0026version_marker\u003dnull\u0027,"}],"source_content_type":"text/x-python","patch_set":3,"id":"8d217132_3cf81ad1","line":2817,"in_reply_to":"cb9633a0_bda51513","updated":"2026-03-31 10:34:18.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":2838,"context_line":"              \u0027marker\u0027: \u0027\\x00obj\\x00:\u0027,"},{"line_number":2839,"context_line":"              \u0027prefix\u0027: \u0027\u0027,"},{"line_number":2840,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2841,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2842,"context_line":""},{"line_number":2843,"context_line":"        # marker and version_marker\u003d\u003cversion\u003e"},{"line_number":2844,"context_line":"        self.app.clear_calls()"}],"source_content_type":"text/x-python","patch_set":3,"id":"e247ecf6_86a31039","line":2841,"updated":"2026-03-26 02:50:53.000000000","message":"this fails with the change reverted:\n\n```\nFAILED swift/test/unit/common/middleware/test_object_versioning.py::ObjectVersioningTestContainerOperations::test_list_versions_marker - AssertionError: Lists differ: [(\u0027/v1/a/c\u0027, {\u0027format\u0027: \u0027json\u0027, \u0027marker\u0027: \u0027obj\u0027, \u0027versio[145 chars]\u0027\u0027})] !\u003d [(\u0027/v1/a/c\u0027, {\u0027versions\u0027: \u0027\u0027, \u0027marker\u0027: \u0027obj\u0027, \u0027version_[136 chars]\u0027\u0027})]\n\nFirst differing element 1:\n(\u0027/v1[18 chars]\u0027, {\u0027delimiter\u0027: \u0027\u0027, \u0027limit\u0027: \u0027\u0027, \u0027marker\u0027: \u0027\\[38 chars] \u0027\u0027})\n(\u0027/v1[18 chars]\u0027, {\u0027prefix\u0027: \u0027\u0027, \u0027marker\u0027: \u0027obj\u0027, \u0027limit\u0027: \u0027\u0027[29 chars] \u0027\u0027})\n\n  [(\u0027/v1/a/c\u0027,\n    {\u0027format\u0027: \u0027json\u0027,\n     \u0027marker\u0027: \u0027obj\u0027,\n     \u0027version_marker\u0027: \u0027null\u0027,\n     \u0027versions\u0027: \u0027\u0027}),\n   (\u0027/v1/a/%00versions%00c\u0027,\n+   {\u0027delimiter\u0027: \u0027\u0027, \u0027limit\u0027: \u0027\u0027, \u0027marker\u0027: \u0027obj\u0027, \u0027prefix\u0027: \u0027\u0027, \u0027reverse\u0027: \u0027\u0027})]\n-   {\u0027delimiter\u0027: \u0027\u0027,\n-    \u0027limit\u0027: \u0027\u0027,\n-    \u0027marker\u0027: \u0027\\x00obj\\x00:\u0027,\n-    \u0027prefix\u0027: \u0027\u0027,\n-    \u0027reverse\u0027: \u0027\u0027})]\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d 1 failed, 1 warning in 0.41s \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n```\n\nthe marker here is \"wrong\"\n\n```\n(Pdb) ![c.req.params.get(\u0027marker\u0027) for c in self.app.call_list]\n[\u0027obj\u0027, \u0027obj\u0027]\n```\n\n^ I think this is sort of \"obviously\" incorrect because the second request is to the version container:\n\n```\n(Pdb) ![c.req.path for c in self.app.call_list]\n[\u0027/v1/a/c\u0027, \u0027/v1/a/%00versions%00c\u0027]\n```\n\nwhy would we EVER want to query the *versions* container with the *user namespace* marker?!\n\nwith this change in place the captured params are *exactly* the same params as the case where we don\u0027t include the `version_marker\u003d` query param:\n\n```\n(Pdb) ![c.req.params.get(\u0027marker\u0027) for c in self.app.call_list]\n[\u0027obj\u0027, \u0027\\x00obj\\x00:\u0027]\n```","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[{"line_number":2838,"context_line":"              \u0027marker\u0027: \u0027\\x00obj\\x00:\u0027,"},{"line_number":2839,"context_line":"              \u0027prefix\u0027: \u0027\u0027,"},{"line_number":2840,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2841,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2842,"context_line":""},{"line_number":2843,"context_line":"        # marker and version_marker\u003d\u003cversion\u003e"},{"line_number":2844,"context_line":"        self.app.clear_calls()"}],"source_content_type":"text/x-python","patch_set":3,"id":"70b76b1d_e9c5b227","line":2841,"in_reply_to":"e247ecf6_86a31039","updated":"2026-03-31 10:34:18.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"513e215bc8ef5f99d5d31031a74847a855228932","unresolved":true,"context_lines":[{"line_number":2876,"context_line":"              \u0027marker\u0027: \u0027\\x00obj\\x009999999989.99999\u0027,"},{"line_number":2877,"context_line":"              \u0027prefix\u0027: \u0027\u0027,"},{"line_number":2878,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2879,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2880,"context_line":""},{"line_number":2881,"context_line":"    def test_list_versions_invalid_delimiter(self):"},{"line_number":2882,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"d44ec5dc_8f12e6fa","line":2879,"updated":"2026-03-26 02:50:53.000000000","message":"FWIW with the change reverted and the previous assert removed on \"master\" we\u0027d expect *this* assertion to be \"correct\"\n\n```\n(vagrant-swift-all-in-one) cgerrard@NVStation:~/Workspace/vagrant-swift-all-in-one/swift$ git diff | cat\ndiff --git a/swift/common/middleware/versioned_writes/object_versioning.py b/swift/common/middleware/versioned_writes/object_versioning.py\nindex 7c125d47ad..3cc0e37df9 100644\n--- a/swift/common/middleware/versioned_writes/object_versioning.py\n+++ b/swift/common/middleware/versioned_writes/object_versioning.py\n@@ -1115,20 +1115,22 @@ class ContainerContext(ObjectVersioningContext):\n         if not req.accept.best_match([\u0027application/json\u0027]):\n             raise HTTPNotAcceptable(request\u003dreq)\n \n-        params \u003d dict(req.params)\n-        if \u0027marker\u0027 in params:\n-            if params.get(\u0027version_marker\u0027) in (\u0027null\u0027, None):\n-                params[\u0027marker\u0027] \u003d self._build_versions_object_prefix(\n-                    params[\u0027marker\u0027]) + \u0027:\u0027  # just past all numbers\n-            else:\n+        params \u003d req.params\n+        if \u0027version_marker\u0027 in params:\n+            if \u0027marker\u0027 not in params:\n+                raise HTTPBadRequest(\u0027version_marker param requires marker\u0027)\n+\n+            if params[\u0027version_marker\u0027] !\u003d \u0027null\u0027:\n                 try:\n                     ts \u003d Timestamp(params.pop(\u0027version_marker\u0027))\n                 except ValueError:\n                     raise HTTPBadRequest(\u0027invalid version_marker param\u0027)\n+\n                 params[\u0027marker\u0027] \u003d self._build_versions_object_name(\n                     params[\u0027marker\u0027], ts.internal)\n-        elif \u0027version_marker\u0027 in params:\n-            raise HTTPBadRequest(\u0027version_marker param requires marker\u0027)\n+        elif \u0027marker\u0027 in params:\n+            params[\u0027marker\u0027] \u003d self._build_versions_object_prefix(\n+                params[\u0027marker\u0027]) + \u0027:\u0027  # just past all numbers\n \n         delim \u003d params.get(\u0027delimiter\u0027, \u0027\u0027)\n         # Exclude the set of chars used in version_id from user delimiters\ndiff --git a/test/unit/common/middleware/test_object_versioning.py b/test/unit/common/middleware/test_object_versioning.py\nindex bb6326d031..2a7de04978 100644\n--- a/test/unit/common/middleware/test_object_versioning.py\n+++ b/test/unit/common/middleware/test_object_versioning.py\n@@ -2826,19 +2826,6 @@ class ObjectVersioningTestContainerOperations(ObjectVersioningBaseTestCase):\n         self.assertEqual(len(self.authorized), 1)\n         self.assertRequestEqual(req, self.authorized[0])\n         self.assertEqual(expected[1:], json.loads(body))\n-        self.assertEqual([\n-            (\u0027/v1/a/c\u0027,\n-             {\u0027format\u0027: \u0027json\u0027,\n-              \u0027marker\u0027: \u0027obj\u0027,\n-              \u0027version_marker\u0027: \u0027null\u0027,\n-              \u0027versions\u0027: \u0027\u0027}),\n-            (\u0027/v1/a/%00versions%00c\u0027,\n-             {\u0027delimiter\u0027: \u0027\u0027,\n-              \u0027limit\u0027: \u0027\u0027,\n-              \u0027marker\u0027: \u0027\\x00obj\\x00:\u0027,\n-              \u0027prefix\u0027: \u0027\u0027,\n-              \u0027reverse\u0027: \u0027\u0027})],\n-            [(call.req.path, call.req.params) for call in self.app.call_list])\n \n         # marker and version_marker\u003d\u003cversion\u003e\n         self.app.clear_calls()\n@@ -2864,6 +2851,8 @@ class ObjectVersioningTestContainerOperations(ObjectVersioningBaseTestCase):\n         self.assertEqual(len(self.authorized), 1)\n         self.assertRequestEqual(req, self.authorized[0])\n         self.assertEqual(expected[3:], json.loads(body))\n+        import pdb\n+        pdb.set_trace()\n         self.assertEqual([\n             (\u0027/v1/a/c\u0027,\n              {\u0027format\u0027: \u0027json\u0027,\n```\n\ni.e.\n\n```\n(Pdb) ![c.req.params.get(\u0027marker\u0027) for c in self.app.call_list]\n[\u0027obj\u0027, \u0027\\x00obj\\x009999999989.99999\u0027]\n```","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"2ac36a80178cd7d6fb0852d9869f4dce9b53f1e9","unresolved":false,"context_lines":[{"line_number":2876,"context_line":"              \u0027marker\u0027: \u0027\\x00obj\\x009999999989.99999\u0027,"},{"line_number":2877,"context_line":"              \u0027prefix\u0027: \u0027\u0027,"},{"line_number":2878,"context_line":"              \u0027reverse\u0027: \u0027\u0027})],"},{"line_number":2879,"context_line":"            [(call.req.path, call.req.params) for call in self.app.call_list])"},{"line_number":2880,"context_line":""},{"line_number":2881,"context_line":"    def test_list_versions_invalid_delimiter(self):"},{"line_number":2882,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"ee2c469c_5da8211d","line":2879,"in_reply_to":"d44ec5dc_8f12e6fa","updated":"2026-03-31 10:34:18.000000000","message":"Acknowledged","commit_id":"52abfba659d1b08c65f7a6dc4d8267c02d0b450b"}]}
