We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
mbsync: Tolerate MusicBrainz recording ID changes
mbsync uses the musicbrainz recording id to match local track information against a certain track in a musicbrainz release. However, there is no fallback matching defined, so once the recording id changes in musicbrainz the local track can no longer be synced unless the recording id is changed manually. But this behavior is also not very visible (I stumbled upon it by accident), so most users would not even be aware that the recording id is outdated.

mbsync: Tolerate MusicBrainz recording ID changes
mbsync uses the musicbrainz recording id to match local track information against a certain track in a musicbrainz release. However, there is no fallback matching defined, so once the recording id changes in musicbrainz the local track can no longer be synced unless the recording id is changed manually. But this behavior is also not very visible (I stumbled upon it by accident), so most users would not even be aware that the recording id is outdated.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `beetsplug/mbsync.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # This file is part of beets.
3 # Copyright 2016, Jakob Schnitzer.
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
15 
16 """Update library's tags using MusicBrainz.
17 """
18 from __future__ import division, absolute_import, print_function
19 
20 from beets.plugins import BeetsPlugin
21 from beets import autotag, library, ui, util
22 from beets.autotag import hooks
23 from collections import defaultdict
24 
25 
26 def apply_item_changes(lib, item, move, pretend, write):
27     """Store, move and write the item according to the arguments.
28     """
29     if not pretend:
30         # Move the item if it's in the library.
31         if move and lib.directory in util.ancestry(item.path):
32             item.move(with_album=False)
33 
34         if write:
35             item.try_write()
36         item.store()
37 
38 
39 class MBSyncPlugin(BeetsPlugin):
40     def __init__(self):
41         super(MBSyncPlugin, self).__init__()
42 
43     def commands(self):
44         cmd = ui.Subcommand('mbsync',
45                             help=u'update metadata from musicbrainz')
46         cmd.parser.add_option(
47             u'-p', u'--pretend', action='store_true',
48             help=u'show all changes but do nothing')
49         cmd.parser.add_option(
50             u'-m', u'--move', action='store_true', dest='move',
51             help=u"move files in the library directory")
52         cmd.parser.add_option(
53             u'-M', u'--nomove', action='store_false', dest='move',
54             help=u"don't move files in library")
55         cmd.parser.add_option(
56             u'-W', u'--nowrite', action='store_false',
57             default=None, dest='write',
58             help=u"don't write updated metadata to files")
59         cmd.parser.add_format_option()
60         cmd.func = self.func
61         return [cmd]
62 
63     def func(self, lib, opts, args):
64         """Command handler for the mbsync function.
65         """
66         move = ui.should_move(opts.move)
67         pretend = opts.pretend
68         write = ui.should_write(opts.write)
69         query = ui.decargs(args)
70 
71         self.singletons(lib, query, move, pretend, write)
72         self.albums(lib, query, move, pretend, write)
73 
74     def singletons(self, lib, query, move, pretend, write):
75         """Retrieve and apply info from the autotagger for items matched by
76         query.
77         """
78         for item in lib.items(query + [u'singleton:true']):
79             item_formatted = format(item)
80             if not item.mb_trackid:
81                 self._log.info(u'Skipping singleton with no mb_trackid: {0}',
82                                item_formatted)
83                 continue
84 
85             # Get the MusicBrainz recording info.
86             track_info = hooks.track_for_mbid(item.mb_trackid)
87             if not track_info:
88                 self._log.info(u'Recording ID not found: {0} for track {0}',
89                                item.mb_trackid,
90                                item_formatted)
91                 continue
92 
93             # Apply.
94             with lib.transaction():
95                 autotag.apply_item_metadata(item, track_info)
96                 apply_item_changes(lib, item, move, pretend, write)
97 
98     def albums(self, lib, query, move, pretend, write):
99         """Retrieve and apply info from the autotagger for albums matched by
100         query and their items.
101         """
102         # Process matching albums.
103         for a in lib.albums(query):
104             album_formatted = format(a)
105             if not a.mb_albumid:
106                 self._log.info(u'Skipping album with no mb_albumid: {0}',
107                                album_formatted)
108                 continue
109 
110             items = list(a.items())
111 
112             # Get the MusicBrainz album information.
113             album_info = hooks.album_for_mbid(a.mb_albumid)
114             if not album_info:
115                 self._log.info(u'Release ID {0} not found for album {1}',
116                                a.mb_albumid,
117                                album_formatted)
118                 continue
119 
120             # Map recording MBIDs to their information. Recordings can appear
121             # multiple times on a release, so each MBID maps to a list of
122             # TrackInfo objects.
123             track_index = defaultdict(list)
124             for track_info in album_info.tracks:
125                 track_index[track_info.track_id].append(track_info)
126 
127             # Construct a track mapping according to MBIDs. This should work
128             # for albums that have missing or extra tracks. If there are
129             # multiple copies of a recording, they are disambiguated using
130             # their disc and track number.
131             mapping = {}
132             for item in items:
133                 candidates = track_index[item.mb_trackid]
134                 if len(candidates) == 1:
135                     mapping[item] = candidates[0]
136                 else:
137                     for c in candidates:
138                         if (c.medium_index == item.track and
139                                 c.medium == item.disc):
140                             mapping[item] = c
141                             break
142 
143             # Apply.
144             self._log.debug(u'applying changes to {}', album_formatted)
145             with lib.transaction():
146                 autotag.apply_metadata(album_info, mapping)
147                 changed = False
148                 for item in items:
149                     item_changed = ui.show_model_changes(item)
150                     changed |= item_changed
151                     if item_changed:
152                         apply_item_changes(lib, item, move, pretend, write)
153 
154                 if not changed:
155                     # No change to any item.
156                     continue
157 
158                 if not pretend:
159                     # Update album structure to reflect an item in it.
160                     for key in library.Album.item_keys:
161                         a[key] = items[0][key]
162                     a.store()
163 
164                     # Move album art (and any inconsistent items).
165                     if move and lib.directory in util.ancestry(items[0].path):
166                         self._log.debug(u'moving album {0}', album_formatted)
167                         a.move()
168
```
--- 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/beetsplug/mbsync.py b/beetsplug/mbsync.py
--- a/beetsplug/mbsync.py
+++ b/beetsplug/mbsync.py
@@ -117,28 +117,35 @@
                                album_formatted)
                 continue
 
-            # Map recording MBIDs to their information. Recordings can appear
-            # multiple times on a release, so each MBID maps to a list of
-            # TrackInfo objects.
+            # Map release track and recording MBIDs to their information.
+            # Recordings can appear multiple times on a release, so each MBID
+            # maps to a list of TrackInfo objects.
+            releasetrack_index = dict()
             track_index = defaultdict(list)
             for track_info in album_info.tracks:
+                releasetrack_index[track_info.release_track_id] = track_info
                 track_index[track_info.track_id].append(track_info)
 
-            # Construct a track mapping according to MBIDs. This should work
-            # for albums that have missing or extra tracks. If there are
-            # multiple copies of a recording, they are disambiguated using
-            # their disc and track number.
+            # Construct a track mapping according to MBIDs (release track MBIDs
+            # first, if available, and recording MBIDs otherwise). This should
+            # work for albums that have missing or extra tracks.
             mapping = {}
             for item in items:
-                candidates = track_index[item.mb_trackid]
-                if len(candidates) == 1:
-                    mapping[item] = candidates[0]
+                if item.mb_releasetrackid and \
+                        item.mb_releasetrackid in releasetrack_index:
+                    mapping[item] = releasetrack_index[item.mb_releasetrackid]
                 else:
-                    for c in candidates:
-                        if (c.medium_index == item.track and
-                                c.medium == item.disc):
-                            mapping[item] = c
-                            break
+                    candidates = track_index[item.mb_trackid]
+                    if len(candidates) == 1:
+                        mapping[item] = candidates[0]
+                    else:
+                        # If there are multiple copies of a recording, they are
+                        # disambiguated using their disc and track number.
+                        for c in candidates:
+                            if (c.medium_index == item.track and
+                                    c.medium == item.disc):
+                                mapping[item] = c
+                                break
 
             # Apply.
             self._log.debug(u'applying changes to {}', album_formatted)

{"golden_diff": "diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py\n--- a/beetsplug/mbsync.py\n+++ b/beetsplug/mbsync.py\n@@ -117,28 +117,35 @@\n                                album_formatted)\n                 continue\n \n-            # Map recording MBIDs to their information. Recordings can appear\n-            # multiple times on a release, so each MBID maps to a list of\n-            # TrackInfo objects.\n+            # Map release track and recording MBIDs to their information.\n+            # Recordings can appear multiple times on a release, so each MBID\n+            # maps to a list of TrackInfo objects.\n+            releasetrack_index = dict()\n             track_index = defaultdict(list)\n             for track_info in album_info.tracks:\n+                releasetrack_index[track_info.release_track_id] = track_info\n                 track_index[track_info.track_id].append(track_info)\n \n-            # Construct a track mapping according to MBIDs. This should work\n-            # for albums that have missing or extra tracks. If there are\n-            # multiple copies of a recording, they are disambiguated using\n-            # their disc and track number.\n+            # Construct a track mapping according to MBIDs (release track MBIDs\n+            # first, if available, and recording MBIDs otherwise). This should\n+            # work for albums that have missing or extra tracks.\n             mapping = {}\n             for item in items:\n-                candidates = track_index[item.mb_trackid]\n-                if len(candidates) == 1:\n-                    mapping[item] = candidates[0]\n+                if item.mb_releasetrackid and \\\n+                        item.mb_releasetrackid in releasetrack_index:\n+                    mapping[item] = releasetrack_index[item.mb_releasetrackid]\n                 else:\n-                    for c in candidates:\n-                        if (c.medium_index == item.track and\n-                                c.medium == item.disc):\n-                            mapping[item] = c\n-                            break\n+                    candidates = track_index[item.mb_trackid]\n+                    if len(candidates) == 1:\n+                        mapping[item] = candidates[0]\n+                    else:\n+                        # If there are multiple copies of a recording, they are\n+                        # disambiguated using their disc and track number.\n+                        for c in candidates:\n+                            if (c.medium_index == item.track and\n+                                    c.medium == item.disc):\n+                                mapping[item] = c\n+                                break\n \n             # Apply.\n             self._log.debug(u'applying changes to {}', album_formatted)\n", "issue": "mbsync: Tolerate MusicBrainz recording ID changes\nmbsync uses the musicbrainz recording id to match local track information against a certain track in a musicbrainz release. However, there is no fallback matching defined, so once the recording id changes in musicbrainz the local track can no longer be synced unless the recording id is changed manually. But this behavior is also not very visible (I stumbled upon it by accident), so most users would not even be aware that the recording id is outdated.\n\nmbsync: Tolerate MusicBrainz recording ID changes\nmbsync uses the musicbrainz recording id to match local track information against a certain track in a musicbrainz release. However, there is no fallback matching defined, so once the recording id changes in musicbrainz the local track can no longer be synced unless the recording id is changed manually. But this behavior is also not very visible (I stumbled upon it by accident), so most users would not even be aware that the recording id is outdated.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# This file is part of beets.\n# Copyright 2016, Jakob Schnitzer.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Update library's tags using MusicBrainz.\n\"\"\"\nfrom __future__ import division, absolute_import, print_function\n\nfrom beets.plugins import BeetsPlugin\nfrom beets import autotag, library, ui, util\nfrom beets.autotag import hooks\nfrom collections import defaultdict\n\n\ndef apply_item_changes(lib, item, move, pretend, write):\n    \"\"\"Store, move and write the item according to the arguments.\n    \"\"\"\n    if not pretend:\n        # Move the item if it's in the library.\n        if move and lib.directory in util.ancestry(item.path):\n            item.move(with_album=False)\n\n        if write:\n            item.try_write()\n        item.store()\n\n\nclass MBSyncPlugin(BeetsPlugin):\n    def __init__(self):\n        super(MBSyncPlugin, self).__init__()\n\n    def commands(self):\n        cmd = ui.Subcommand('mbsync',\n                            help=u'update metadata from musicbrainz')\n        cmd.parser.add_option(\n            u'-p', u'--pretend', action='store_true',\n            help=u'show all changes but do nothing')\n        cmd.parser.add_option(\n            u'-m', u'--move', action='store_true', dest='move',\n            help=u\"move files in the library directory\")\n        cmd.parser.add_option(\n            u'-M', u'--nomove', action='store_false', dest='move',\n            help=u\"don't move files in library\")\n        cmd.parser.add_option(\n            u'-W', u'--nowrite', action='store_false',\n            default=None, dest='write',\n            help=u\"don't write updated metadata to files\")\n        cmd.parser.add_format_option()\n        cmd.func = self.func\n        return [cmd]\n\n    def func(self, lib, opts, args):\n        \"\"\"Command handler for the mbsync function.\n        \"\"\"\n        move = ui.should_move(opts.move)\n        pretend = opts.pretend\n        write = ui.should_write(opts.write)\n        query = ui.decargs(args)\n\n        self.singletons(lib, query, move, pretend, write)\n        self.albums(lib, query, move, pretend, write)\n\n    def singletons(self, lib, query, move, pretend, write):\n        \"\"\"Retrieve and apply info from the autotagger for items matched by\n        query.\n        \"\"\"\n        for item in lib.items(query + [u'singleton:true']):\n            item_formatted = format(item)\n            if not item.mb_trackid:\n                self._log.info(u'Skipping singleton with no mb_trackid: {0}',\n                               item_formatted)\n                continue\n\n            # Get the MusicBrainz recording info.\n            track_info = hooks.track_for_mbid(item.mb_trackid)\n            if not track_info:\n                self._log.info(u'Recording ID not found: {0} for track {0}',\n                               item.mb_trackid,\n                               item_formatted)\n                continue\n\n            # Apply.\n            with lib.transaction():\n                autotag.apply_item_metadata(item, track_info)\n                apply_item_changes(lib, item, move, pretend, write)\n\n    def albums(self, lib, query, move, pretend, write):\n        \"\"\"Retrieve and apply info from the autotagger for albums matched by\n        query and their items.\n        \"\"\"\n        # Process matching albums.\n        for a in lib.albums(query):\n            album_formatted = format(a)\n            if not a.mb_albumid:\n                self._log.info(u'Skipping album with no mb_albumid: {0}',\n                               album_formatted)\n                continue\n\n            items = list(a.items())\n\n            # Get the MusicBrainz album information.\n            album_info = hooks.album_for_mbid(a.mb_albumid)\n            if not album_info:\n                self._log.info(u'Release ID {0} not found for album {1}',\n                               a.mb_albumid,\n                               album_formatted)\n                continue\n\n            # Map recording MBIDs to their information. Recordings can appear\n            # multiple times on a release, so each MBID maps to a list of\n            # TrackInfo objects.\n            track_index = defaultdict(list)\n            for track_info in album_info.tracks:\n                track_index[track_info.track_id].append(track_info)\n\n            # Construct a track mapping according to MBIDs. This should work\n            # for albums that have missing or extra tracks. If there are\n            # multiple copies of a recording, they are disambiguated using\n            # their disc and track number.\n            mapping = {}\n            for item in items:\n                candidates = track_index[item.mb_trackid]\n                if len(candidates) == 1:\n                    mapping[item] = candidates[0]\n                else:\n                    for c in candidates:\n                        if (c.medium_index == item.track and\n                                c.medium == item.disc):\n                            mapping[item] = c\n                            break\n\n            # Apply.\n            self._log.debug(u'applying changes to {}', album_formatted)\n            with lib.transaction():\n                autotag.apply_metadata(album_info, mapping)\n                changed = False\n                for item in items:\n                    item_changed = ui.show_model_changes(item)\n                    changed |= item_changed\n                    if item_changed:\n                        apply_item_changes(lib, item, move, pretend, write)\n\n                if not changed:\n                    # No change to any item.\n                    continue\n\n                if not pretend:\n                    # Update album structure to reflect an item in it.\n                    for key in library.Album.item_keys:\n                        a[key] = items[0][key]\n                    a.store()\n\n                    # Move album art (and any inconsistent items).\n                    if move and lib.directory in util.ancestry(items[0].path):\n                        self._log.debug(u'moving album {0}', album_formatted)\n                        a.move()\n", "path": "beetsplug/mbsync.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# This file is part of beets.\n# Copyright 2016, Jakob Schnitzer.\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n\n\"\"\"Update library's tags using MusicBrainz.\n\"\"\"\nfrom __future__ import division, absolute_import, print_function\n\nfrom beets.plugins import BeetsPlugin\nfrom beets import autotag, library, ui, util\nfrom beets.autotag import hooks\nfrom collections import defaultdict\n\n\ndef apply_item_changes(lib, item, move, pretend, write):\n    \"\"\"Store, move and write the item according to the arguments.\n    \"\"\"\n    if not pretend:\n        # Move the item if it's in the library.\n        if move and lib.directory in util.ancestry(item.path):\n            item.move(with_album=False)\n\n        if write:\n            item.try_write()\n        item.store()\n\n\nclass MBSyncPlugin(BeetsPlugin):\n    def __init__(self):\n        super(MBSyncPlugin, self).__init__()\n\n    def commands(self):\n        cmd = ui.Subcommand('mbsync',\n                            help=u'update metadata from musicbrainz')\n        cmd.parser.add_option(\n            u'-p', u'--pretend', action='store_true',\n            help=u'show all changes but do nothing')\n        cmd.parser.add_option(\n            u'-m', u'--move', action='store_true', dest='move',\n            help=u\"move files in the library directory\")\n        cmd.parser.add_option(\n            u'-M', u'--nomove', action='store_false', dest='move',\n            help=u\"don't move files in library\")\n        cmd.parser.add_option(\n            u'-W', u'--nowrite', action='store_false',\n            default=None, dest='write',\n            help=u\"don't write updated metadata to files\")\n        cmd.parser.add_format_option()\n        cmd.func = self.func\n        return [cmd]\n\n    def func(self, lib, opts, args):\n        \"\"\"Command handler for the mbsync function.\n        \"\"\"\n        move = ui.should_move(opts.move)\n        pretend = opts.pretend\n        write = ui.should_write(opts.write)\n        query = ui.decargs(args)\n\n        self.singletons(lib, query, move, pretend, write)\n        self.albums(lib, query, move, pretend, write)\n\n    def singletons(self, lib, query, move, pretend, write):\n        \"\"\"Retrieve and apply info from the autotagger for items matched by\n        query.\n        \"\"\"\n        for item in lib.items(query + [u'singleton:true']):\n            item_formatted = format(item)\n            if not item.mb_trackid:\n                self._log.info(u'Skipping singleton with no mb_trackid: {0}',\n                               item_formatted)\n                continue\n\n            # Get the MusicBrainz recording info.\n            track_info = hooks.track_for_mbid(item.mb_trackid)\n            if not track_info:\n                self._log.info(u'Recording ID not found: {0} for track {0}',\n                               item.mb_trackid,\n                               item_formatted)\n                continue\n\n            # Apply.\n            with lib.transaction():\n                autotag.apply_item_metadata(item, track_info)\n                apply_item_changes(lib, item, move, pretend, write)\n\n    def albums(self, lib, query, move, pretend, write):\n        \"\"\"Retrieve and apply info from the autotagger for albums matched by\n        query and their items.\n        \"\"\"\n        # Process matching albums.\n        for a in lib.albums(query):\n            album_formatted = format(a)\n            if not a.mb_albumid:\n                self._log.info(u'Skipping album with no mb_albumid: {0}',\n                               album_formatted)\n                continue\n\n            items = list(a.items())\n\n            # Get the MusicBrainz album information.\n            album_info = hooks.album_for_mbid(a.mb_albumid)\n            if not album_info:\n                self._log.info(u'Release ID {0} not found for album {1}',\n                               a.mb_albumid,\n                               album_formatted)\n                continue\n\n            # Map release track and recording MBIDs to their information.\n            # Recordings can appear multiple times on a release, so each MBID\n            # maps to a list of TrackInfo objects.\n            releasetrack_index = dict()\n            track_index = defaultdict(list)\n            for track_info in album_info.tracks:\n                releasetrack_index[track_info.release_track_id] = track_info\n                track_index[track_info.track_id].append(track_info)\n\n            # Construct a track mapping according to MBIDs (release track MBIDs\n            # first, if available, and recording MBIDs otherwise). This should\n            # work for albums that have missing or extra tracks.\n            mapping = {}\n            for item in items:\n                if item.mb_releasetrackid and \\\n                        item.mb_releasetrackid in releasetrack_index:\n                    mapping[item] = releasetrack_index[item.mb_releasetrackid]\n                else:\n                    candidates = track_index[item.mb_trackid]\n                    if len(candidates) == 1:\n                        mapping[item] = candidates[0]\n                    else:\n                        # If there are multiple copies of a recording, they are\n                        # disambiguated using their disc and track number.\n                        for c in candidates:\n                            if (c.medium_index == item.track and\n                                    c.medium == item.disc):\n                                mapping[item] = c\n                                break\n\n            # Apply.\n            self._log.debug(u'applying changes to {}', album_formatted)\n            with lib.transaction():\n                autotag.apply_metadata(album_info, mapping)\n                changed = False\n                for item in items:\n                    item_changed = ui.show_model_changes(item)\n                    changed |= item_changed\n                    if item_changed:\n                        apply_item_changes(lib, item, move, pretend, write)\n\n                if not changed:\n                    # No change to any item.\n                    continue\n\n                if not pretend:\n                    # Update album structure to reflect an item in it.\n                    for key in library.Album.item_keys:\n                        a[key] = items[0][key]\n                    a.store()\n\n                    # Move album art (and any inconsistent items).\n                    if move and lib.directory in util.ancestry(items[0].path):\n                        self._log.debug(u'moving album {0}', album_formatted)\n                        a.move()\n", "path": "beetsplug/mbsync.py"}]}