We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
aws s3 sync --delete behaves differently if source is <LocalPath> rather than <S3Path>
This issue was raised on: https://stackoverflow.com/questions/32304326/amazon-s3-sync-deleting-excluded-files

Basically, doing `aws s3 sync . s3://bucket2 --delete` behaves differently to `aws s3 sync s3://bucket1 s3://bucket2 --delete` even if the two source directories are identical.

It would appear that the logic for `--delete` behaves differently between Local and S3. An example is well-documented in the stackoverflow issue.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `awscli/customizations/s3/filters.py`
Content:
```
1 # Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"). You
4 # may not use this file except in compliance with the License. A copy of
5 # the License is located at
6 #
7 #     http://aws.amazon.com/apache2.0/
8 #
9 # or in the "license" file accompanying this file. This file is
10 # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 # ANY KIND, either express or implied. See the License for the specific
12 # language governing permissions and limitations under the License.
13 import logging
14 import fnmatch
15 import os
16 
17 from awscli.customizations.s3.utils import split_s3_bucket_key
18 
19 
20 LOG = logging.getLogger(__name__)
21 
22 
23 def create_filter(parameters):
24     """Given the CLI parameters dict, create a Filter object."""
25     # We need to evaluate all the filters based on the source
26     # directory.
27     if parameters['filters']:
28         cli_filters = parameters['filters']
29         real_filters = []
30         for filter_type, filter_pattern in cli_filters:
31             real_filters.append((filter_type.lstrip('-'),
32                                  filter_pattern))
33         source_location = parameters['src']
34         if source_location.startswith('s3://'):
35             # This gives us (bucket, keyname) and we want
36             # the bucket to be the root dir.
37             src_rootdir = _get_s3_root(source_location,
38                                        parameters['dir_op'])
39             dst_rootdir = _get_local_root(parameters['dest'],
40                                           parameters['dir_op'])
41         else:
42             src_rootdir = _get_local_root(parameters['src'], parameters['dir_op'])
43             dst_rootdir = _get_s3_root(parameters['dest'],
44                                        parameters['dir_op'])
45 
46         return Filter(real_filters, src_rootdir, dst_rootdir)
47     else:
48         return Filter({}, None, None)
49 
50 
51 def _get_s3_root(source_location, dir_op):
52     # Obtain the bucket and the key.
53     bucket, key = split_s3_bucket_key(source_location)
54     if not dir_op and not key.endswith('/'):
55         # If we are not performing an operation on a directory and the key
56         # is of the form: ``prefix/key``. We only want ``prefix`` included in
57         # the the s3 root and not ``key``.
58         key = '/'.join(key.split('/')[:-1])
59     # Rejoin the bucket and key back together.
60     s3_path = '/'.join([bucket, key])
61     return s3_path
62 
63 
64 def _get_local_root(source_location, dir_op):
65     if dir_op:
66         rootdir = os.path.abspath(source_location)
67     else:
68         rootdir = os.path.abspath(os.path.dirname(source_location))
69     return rootdir
70 
71 
72 class Filter(object):
73     """
74     This is a universal exclude/include filter.
75     """
76     def __init__(self, patterns, rootdir, dst_rootdir):
77         """
78         :var patterns: A list of patterns. A pattern consits of a list
79             whose first member is a string 'exclude' or 'include'.
80             The second member is the actual rule.
81         :var rootdir: The root directory where the patterns are evaluated.
82             This will generally be the directory of the source location.
83         :var dst_rootdir: The destination root directory where the patterns are
84             evaluated.  This is only useful when the --delete option is
85             also specified.
86 
87         """
88         self._original_patterns = patterns
89         self.patterns = self._full_path_patterns(patterns, rootdir)
90         self.dst_patterns = self._full_path_patterns(patterns, dst_rootdir)
91 
92     def _full_path_patterns(self, original_patterns, rootdir):
93         # We need to transform the patterns into patterns that have
94         # the root dir prefixed, so things like ``--exclude "*"``
95         # will actually be ['exclude', '/path/to/root/*']
96         full_patterns = []
97         for pattern in original_patterns:
98             full_patterns.append(
99                 (pattern[0], os.path.join(rootdir, pattern[1])))
100         return full_patterns
101 
102     def call(self, file_infos):
103         """
104         This function iterates over through the yielded file_info objects.  It
105         determines the type of the file and applies pattern matching to
106         determine if the rule applies.  While iterating though the patterns the
107         file is assigned a boolean flag to determine if a file should be
108         yielded on past the filer.  Anything identified by the exclude filter
109         has its flag set to false.  Anything identified by the include filter
110         has its flag set to True.  All files begin with the flag set to true.
111         Rules listed at the end will overwrite flags thrown by rules listed
112         before it.
113         """
114         for file_info in file_infos:
115             file_path = file_info.src
116             file_status = (file_info, True)
117             for pattern, dst_pattern in zip(self.patterns, self.dst_patterns):
118                 current_file_status = self._match_pattern(pattern, file_info)
119                 if current_file_status is not None:
120                     file_status = current_file_status
121                 dst_current_file_status = self._match_pattern(dst_pattern, file_info)
122                 if dst_current_file_status is not None:
123                     file_status = dst_current_file_status
124             LOG.debug("=%s final filtered status, should_include: %s",
125                       file_path, file_status[1])
126             if file_status[1]:
127                 yield file_info
128 
129     def _match_pattern(self, pattern, file_info):
130         file_status = None
131         file_path = file_info.src
132         pattern_type = pattern[0]
133         if file_info.src_type == 'local':
134             path_pattern = pattern[1].replace('/', os.sep)
135         else:
136             path_pattern = pattern[1].replace(os.sep, '/')
137         is_match = fnmatch.fnmatch(file_path, path_pattern)
138         if is_match and pattern_type == 'include':
139             file_status = (file_info, True)
140             LOG.debug("%s matched include filter: %s",
141                         file_path, path_pattern)
142         elif is_match and pattern_type == 'exclude':
143             file_status = (file_info, False)
144             LOG.debug("%s matched exclude filter: %s",
145                         file_path, path_pattern)
146         else:
147             LOG.debug("%s did not match %s filter: %s",
148                         file_path, pattern_type[2:], path_pattern)
149         return file_status
150
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/awscli/customizations/s3/filters.py b/awscli/customizations/s3/filters.py
--- a/awscli/customizations/s3/filters.py
+++ b/awscli/customizations/s3/filters.py
@@ -36,12 +36,16 @@
             # the bucket to be the root dir.
             src_rootdir = _get_s3_root(source_location,
                                        parameters['dir_op'])
-            dst_rootdir = _get_local_root(parameters['dest'],
-                                          parameters['dir_op'])
         else:
             src_rootdir = _get_local_root(parameters['src'], parameters['dir_op'])
+
+        destination_location = parameters['dest']
+        if destination_location.startswith('s3://'):
             dst_rootdir = _get_s3_root(parameters['dest'],
                                        parameters['dir_op'])
+        else:
+            dst_rootdir = _get_local_root(parameters['dest'],
+                                          parameters['dir_op'])
 
         return Filter(real_filters, src_rootdir, dst_rootdir)
     else:

{"golden_diff": "diff --git a/awscli/customizations/s3/filters.py b/awscli/customizations/s3/filters.py\n--- a/awscli/customizations/s3/filters.py\n+++ b/awscli/customizations/s3/filters.py\n@@ -36,12 +36,16 @@\n             # the bucket to be the root dir.\n             src_rootdir = _get_s3_root(source_location,\n                                        parameters['dir_op'])\n-            dst_rootdir = _get_local_root(parameters['dest'],\n-                                          parameters['dir_op'])\n         else:\n             src_rootdir = _get_local_root(parameters['src'], parameters['dir_op'])\n+\n+        destination_location = parameters['dest']\n+        if destination_location.startswith('s3://'):\n             dst_rootdir = _get_s3_root(parameters['dest'],\n                                        parameters['dir_op'])\n+        else:\n+            dst_rootdir = _get_local_root(parameters['dest'],\n+                                          parameters['dir_op'])\n \n         return Filter(real_filters, src_rootdir, dst_rootdir)\n     else:\n", "issue": "aws s3 sync --delete behaves differently if source is <LocalPath> rather than <S3Path>\nThis issue was raised on: https://stackoverflow.com/questions/32304326/amazon-s3-sync-deleting-excluded-files\n\nBasically, doing `aws s3 sync . s3://bucket2 --delete` behaves differently to `aws s3 sync s3://bucket1 s3://bucket2 --delete` even if the two source directories are identical.\n\nIt would appear that the logic for `--delete` behaves differently between Local and S3. An example is well-documented in the stackoverflow issue.\n\n", "before_files": [{"content": "# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"). You\n# may not use this file except in compliance with the License. A copy of\n# the License is located at\n#\n#     http://aws.amazon.com/apache2.0/\n#\n# or in the \"license\" file accompanying this file. This file is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n# ANY KIND, either express or implied. See the License for the specific\n# language governing permissions and limitations under the License.\nimport logging\nimport fnmatch\nimport os\n\nfrom awscli.customizations.s3.utils import split_s3_bucket_key\n\n\nLOG = logging.getLogger(__name__)\n\n\ndef create_filter(parameters):\n    \"\"\"Given the CLI parameters dict, create a Filter object.\"\"\"\n    # We need to evaluate all the filters based on the source\n    # directory.\n    if parameters['filters']:\n        cli_filters = parameters['filters']\n        real_filters = []\n        for filter_type, filter_pattern in cli_filters:\n            real_filters.append((filter_type.lstrip('-'),\n                                 filter_pattern))\n        source_location = parameters['src']\n        if source_location.startswith('s3://'):\n            # This gives us (bucket, keyname) and we want\n            # the bucket to be the root dir.\n            src_rootdir = _get_s3_root(source_location,\n                                       parameters['dir_op'])\n            dst_rootdir = _get_local_root(parameters['dest'],\n                                          parameters['dir_op'])\n        else:\n            src_rootdir = _get_local_root(parameters['src'], parameters['dir_op'])\n            dst_rootdir = _get_s3_root(parameters['dest'],\n                                       parameters['dir_op'])\n\n        return Filter(real_filters, src_rootdir, dst_rootdir)\n    else:\n        return Filter({}, None, None)\n\n\ndef _get_s3_root(source_location, dir_op):\n    # Obtain the bucket and the key.\n    bucket, key = split_s3_bucket_key(source_location)\n    if not dir_op and not key.endswith('/'):\n        # If we are not performing an operation on a directory and the key\n        # is of the form: ``prefix/key``. We only want ``prefix`` included in\n        # the the s3 root and not ``key``.\n        key = '/'.join(key.split('/')[:-1])\n    # Rejoin the bucket and key back together.\n    s3_path = '/'.join([bucket, key])\n    return s3_path\n\n\ndef _get_local_root(source_location, dir_op):\n    if dir_op:\n        rootdir = os.path.abspath(source_location)\n    else:\n        rootdir = os.path.abspath(os.path.dirname(source_location))\n    return rootdir\n\n\nclass Filter(object):\n    \"\"\"\n    This is a universal exclude/include filter.\n    \"\"\"\n    def __init__(self, patterns, rootdir, dst_rootdir):\n        \"\"\"\n        :var patterns: A list of patterns. A pattern consits of a list\n            whose first member is a string 'exclude' or 'include'.\n            The second member is the actual rule.\n        :var rootdir: The root directory where the patterns are evaluated.\n            This will generally be the directory of the source location.\n        :var dst_rootdir: The destination root directory where the patterns are\n            evaluated.  This is only useful when the --delete option is\n            also specified.\n\n        \"\"\"\n        self._original_patterns = patterns\n        self.patterns = self._full_path_patterns(patterns, rootdir)\n        self.dst_patterns = self._full_path_patterns(patterns, dst_rootdir)\n\n    def _full_path_patterns(self, original_patterns, rootdir):\n        # We need to transform the patterns into patterns that have\n        # the root dir prefixed, so things like ``--exclude \"*\"``\n        # will actually be ['exclude', '/path/to/root/*']\n        full_patterns = []\n        for pattern in original_patterns:\n            full_patterns.append(\n                (pattern[0], os.path.join(rootdir, pattern[1])))\n        return full_patterns\n\n    def call(self, file_infos):\n        \"\"\"\n        This function iterates over through the yielded file_info objects.  It\n        determines the type of the file and applies pattern matching to\n        determine if the rule applies.  While iterating though the patterns the\n        file is assigned a boolean flag to determine if a file should be\n        yielded on past the filer.  Anything identified by the exclude filter\n        has its flag set to false.  Anything identified by the include filter\n        has its flag set to True.  All files begin with the flag set to true.\n        Rules listed at the end will overwrite flags thrown by rules listed\n        before it.\n        \"\"\"\n        for file_info in file_infos:\n            file_path = file_info.src\n            file_status = (file_info, True)\n            for pattern, dst_pattern in zip(self.patterns, self.dst_patterns):\n                current_file_status = self._match_pattern(pattern, file_info)\n                if current_file_status is not None:\n                    file_status = current_file_status\n                dst_current_file_status = self._match_pattern(dst_pattern, file_info)\n                if dst_current_file_status is not None:\n                    file_status = dst_current_file_status\n            LOG.debug(\"=%s final filtered status, should_include: %s\",\n                      file_path, file_status[1])\n            if file_status[1]:\n                yield file_info\n\n    def _match_pattern(self, pattern, file_info):\n        file_status = None\n        file_path = file_info.src\n        pattern_type = pattern[0]\n        if file_info.src_type == 'local':\n            path_pattern = pattern[1].replace('/', os.sep)\n        else:\n            path_pattern = pattern[1].replace(os.sep, '/')\n        is_match = fnmatch.fnmatch(file_path, path_pattern)\n        if is_match and pattern_type == 'include':\n            file_status = (file_info, True)\n            LOG.debug(\"%s matched include filter: %s\",\n                        file_path, path_pattern)\n        elif is_match and pattern_type == 'exclude':\n            file_status = (file_info, False)\n            LOG.debug(\"%s matched exclude filter: %s\",\n                        file_path, path_pattern)\n        else:\n            LOG.debug(\"%s did not match %s filter: %s\",\n                        file_path, pattern_type[2:], path_pattern)\n        return file_status\n", "path": "awscli/customizations/s3/filters.py"}], "after_files": [{"content": "# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"). You\n# may not use this file except in compliance with the License. A copy of\n# the License is located at\n#\n#     http://aws.amazon.com/apache2.0/\n#\n# or in the \"license\" file accompanying this file. This file is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n# ANY KIND, either express or implied. See the License for the specific\n# language governing permissions and limitations under the License.\nimport logging\nimport fnmatch\nimport os\n\nfrom awscli.customizations.s3.utils import split_s3_bucket_key\n\n\nLOG = logging.getLogger(__name__)\n\n\ndef create_filter(parameters):\n    \"\"\"Given the CLI parameters dict, create a Filter object.\"\"\"\n    # We need to evaluate all the filters based on the source\n    # directory.\n    if parameters['filters']:\n        cli_filters = parameters['filters']\n        real_filters = []\n        for filter_type, filter_pattern in cli_filters:\n            real_filters.append((filter_type.lstrip('-'),\n                                 filter_pattern))\n        source_location = parameters['src']\n        if source_location.startswith('s3://'):\n            # This gives us (bucket, keyname) and we want\n            # the bucket to be the root dir.\n            src_rootdir = _get_s3_root(source_location,\n                                       parameters['dir_op'])\n        else:\n            src_rootdir = _get_local_root(parameters['src'], parameters['dir_op'])\n\n        destination_location = parameters['dest']\n        if destination_location.startswith('s3://'):\n            dst_rootdir = _get_s3_root(parameters['dest'],\n                                       parameters['dir_op'])\n        else:\n            dst_rootdir = _get_local_root(parameters['dest'],\n                                          parameters['dir_op'])\n\n        return Filter(real_filters, src_rootdir, dst_rootdir)\n    else:\n        return Filter({}, None, None)\n\n\ndef _get_s3_root(source_location, dir_op):\n    # Obtain the bucket and the key.\n    bucket, key = split_s3_bucket_key(source_location)\n    if not dir_op and not key.endswith('/'):\n        # If we are not performing an operation on a directory and the key\n        # is of the form: ``prefix/key``. We only want ``prefix`` included in\n        # the the s3 root and not ``key``.\n        key = '/'.join(key.split('/')[:-1])\n    # Rejoin the bucket and key back together.\n    s3_path = '/'.join([bucket, key])\n    return s3_path\n\n\ndef _get_local_root(source_location, dir_op):\n    if dir_op:\n        rootdir = os.path.abspath(source_location)\n    else:\n        rootdir = os.path.abspath(os.path.dirname(source_location))\n    return rootdir\n\n\nclass Filter(object):\n    \"\"\"\n    This is a universal exclude/include filter.\n    \"\"\"\n    def __init__(self, patterns, rootdir, dst_rootdir):\n        \"\"\"\n        :var patterns: A list of patterns. A pattern consits of a list\n            whose first member is a string 'exclude' or 'include'.\n            The second member is the actual rule.\n        :var rootdir: The root directory where the patterns are evaluated.\n            This will generally be the directory of the source location.\n        :var dst_rootdir: The destination root directory where the patterns are\n            evaluated.  This is only useful when the --delete option is\n            also specified.\n\n        \"\"\"\n        self._original_patterns = patterns\n        self.patterns = self._full_path_patterns(patterns, rootdir)\n        self.dst_patterns = self._full_path_patterns(patterns, dst_rootdir)\n\n    def _full_path_patterns(self, original_patterns, rootdir):\n        # We need to transform the patterns into patterns that have\n        # the root dir prefixed, so things like ``--exclude \"*\"``\n        # will actually be ['exclude', '/path/to/root/*']\n        full_patterns = []\n        for pattern in original_patterns:\n            full_patterns.append(\n                (pattern[0], os.path.join(rootdir, pattern[1])))\n        return full_patterns\n\n    def call(self, file_infos):\n        \"\"\"\n        This function iterates over through the yielded file_info objects.  It\n        determines the type of the file and applies pattern matching to\n        determine if the rule applies.  While iterating though the patterns the\n        file is assigned a boolean flag to determine if a file should be\n        yielded on past the filer.  Anything identified by the exclude filter\n        has its flag set to false.  Anything identified by the include filter\n        has its flag set to True.  All files begin with the flag set to true.\n        Rules listed at the end will overwrite flags thrown by rules listed\n        before it.\n        \"\"\"\n        for file_info in file_infos:\n            file_path = file_info.src\n            file_status = (file_info, True)\n            for pattern, dst_pattern in zip(self.patterns, self.dst_patterns):\n                current_file_status = self._match_pattern(pattern, file_info)\n                if current_file_status is not None:\n                    file_status = current_file_status\n                dst_current_file_status = self._match_pattern(dst_pattern, file_info)\n                if dst_current_file_status is not None:\n                    file_status = dst_current_file_status\n            LOG.debug(\"=%s final filtered status, should_include: %s\",\n                      file_path, file_status[1])\n            if file_status[1]:\n                yield file_info\n\n    def _match_pattern(self, pattern, file_info):\n        file_status = None\n        file_path = file_info.src\n        pattern_type = pattern[0]\n        if file_info.src_type == 'local':\n            path_pattern = pattern[1].replace('/', os.sep)\n        else:\n            path_pattern = pattern[1].replace(os.sep, '/')\n        is_match = fnmatch.fnmatch(file_path, path_pattern)\n        if is_match and pattern_type == 'include':\n            file_status = (file_info, True)\n            LOG.debug(\"%s matched include filter: %s\",\n                        file_path, path_pattern)\n        elif is_match and pattern_type == 'exclude':\n            file_status = (file_info, False)\n            LOG.debug(\"%s matched exclude filter: %s\",\n                        file_path, path_pattern)\n        else:\n            LOG.debug(\"%s did not match %s filter: %s\",\n                        file_path, pattern_type[2:], path_pattern)\n        return file_status\n", "path": "awscli/customizations/s3/filters.py"}]}