We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
migrations need version annotations
We have cases where migrations are non-idempotent, so we should only run them when upgrading from a particular version to a different one.

My suggestion is to write a `qtile_version = "$whatever` into users' configs after they've done a migration, and then we can parse that and only run migrations newer than that.

We'll need to annotate the migrations we have somehow, but we can do that reasonably easily (a decorator, make them callable classes, whatever).

Thoughts?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `libqtile/scripts/migrate.py`
Content:
```
1 # Copyright (c) 2021, Tycho Andersen. All rights reserved.
2 #
3 # Permission is hereby granted, free of charge, to any person obtaining a copy
4 # of this software and associated documentation files (the "Software"), to deal
5 # in the Software without restriction, including without limitation the rights
6 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 # copies of the Software, and to permit persons to whom the Software is
8 # furnished to do so, subject to the following conditions:
9 #
10 # The above copyright notice and this permission notice shall be included in
11 # all copies or substantial portions of the Software.
12 #
13 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 import filecmp
20 import os
21 import os.path
22 import shutil
23 import sys
24 from glob import glob
25 
26 BACKUP_SUFFIX = ".migrate.bak"
27 
28 try:
29     import bowler
30 except ImportError:
31     pass
32 
33 
34 def rename_hook(query, fro, to):
35     # could match on dotted_name< 'hook' '.' 'subscribe' '.' '{name}' >
36     # but the replacement gets more complicated...
37     selector = "'{name}'".format(name=fro)
38     q = query.select_pattern(selector)
39     q.current.kwargs["name"] = fro
40     return q.rename(to)
41 
42 
43 def client_name_updated(query):
44     """ Rename window_name_change -> client_name_updated"""
45     return rename_hook(query, "window_name_change", "client_name_updated")
46 
47 
48 def tile_master_windows_rename(query):
49     return (
50         query
51         .select_function("Tile")
52         .modify_argument("masterWindows", "master_length")
53     )
54 
55 
56 def threaded_poll_text_rename(query):
57     return (
58         query
59         .select_class("ThreadedPollText")
60         .rename("ThreadPoolText")
61     )
62 
63 
64 def pacman_to_checkupdates(query):
65     return (
66         query
67         .select_class("Pacman")
68         .rename("CheckUpdates")
69     )
70 
71 
72 def hook_main_function(query):
73     def modify_main(node, capture, filename):
74         main = capture.get("function_def")
75         if main.prev_sibling:
76             for leaf in main.prev_sibling.leaves():
77                 if "startup" == leaf.value:
78                     return
79         args = capture.get("function_arguments")
80         if args:
81             args[0].remove()
82             main.prefix += "from libqtile import hook, qtile\n"
83             main.prefix += "@hook.subscribe.startup\n"
84 
85     return (
86         query
87         .select_function("main")
88         .is_def()
89         .modify(modify_main)
90     )
91 
92 
93 # Deprecated new_at_current key replaced by new_client_position.
94 # In the node, we want to change the key name
95 # and adapts its value depending of the previous value :
96 #   new_at_current=True => new_client_position=before_current
97 #   new_at_current<>True => new_client_position=after_current
98 def update_node_nac(node, capture, filename):
99     key = capture.get("k")
100     key.value = "new_client_position"
101     val = capture.get("v")
102     if val.value == "True":
103         val.value = "'before_current'"
104     else:
105         val.value = "'after_current'"
106 
107 
108 def new_at_current_to_new_client_position(query):
109     old_pattern = """
110         argument< k="new_at_current" "=" v=any >
111     """
112     return (
113         query
114         .select(old_pattern)
115         .modify(update_node_nac)
116     )
117 
118 
119 MIGRATIONS = [
120     client_name_updated,
121     tile_master_windows_rename,
122     threaded_poll_text_rename,
123     pacman_to_checkupdates,
124     hook_main_function,
125     new_at_current_to_new_client_position,
126 ]
127 
128 
129 MODULE_RENAMES = [
130     ("libqtile.command_graph", "libqtile.command.graph"),
131     ("libqtile.command_client", "libqtile.command.client"),
132     ("libqtile.command_interface", "libqtile.command.interface"),
133     ("libqtile.command_object", "libqtile.command.base"),
134     ("libqtile.window", "libqtile.backend.x11.window"),
135 ]
136 
137 for (fro, to) in MODULE_RENAMES:
138     def f(query, fro=fro, to=to):
139         return (
140             query
141             .select_module(fro)
142             .rename(to)
143         )
144     MIGRATIONS.append(f)
145 
146 
147 def file_and_backup(config_dir):
148     for py in glob(os.path.join(config_dir, "*.py")):
149         backup = py + BACKUP_SUFFIX
150         yield py, backup
151 
152 
153 def do_migrate(args):
154     if "bowler" not in sys.modules:
155         print("bowler can't be found, not migrating config file")
156         print("install it and try again")
157         sys.exit(1)
158 
159     config_dir = os.path.dirname(args.config)
160     for py, backup in file_and_backup(config_dir):
161         shutil.copyfile(py, backup)
162 
163     for m in MIGRATIONS:
164         q = bowler.Query(config_dir)
165         m(q).execute(interactive=args.interactive, write=True)
166 
167     changed = False
168     for py, backup in file_and_backup(config_dir):
169         backup = py + BACKUP_SUFFIX
170         if not filecmp.cmp(py, backup, shallow=False):
171             changed = True
172             break
173 
174     if not changed:
175         print("Config unchanged.")
176         for _, backup in file_and_backup(config_dir):
177             os.remove(backup)
178 
179 
180 def add_subcommand(subparsers, parents):
181     parser = subparsers.add_parser(
182         "migrate",
183         parents=parents,
184         help="Migrate a configuration file to the current API"
185     )
186     parser.add_argument(
187         "-c",
188         "--config",
189         action="store",
190         default=os.path.expanduser(
191             os.path.join(os.getenv("XDG_CONFIG_HOME", "~/.config"), "qtile", "config.py")
192         ),
193         help="Use the specified configuration file (migrates every .py file in this directory)",
194     )
195     parser.add_argument(
196         "--interactive",
197         action="store_true",
198         help="Interactively apply diff (similar to git add -p)",
199     )
200     parser.set_defaults(func=do_migrate)
201
```
--- 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/libqtile/scripts/migrate.py b/libqtile/scripts/migrate.py
--- a/libqtile/scripts/migrate.py
+++ b/libqtile/scripts/migrate.py
@@ -162,7 +162,7 @@
 
     for m in MIGRATIONS:
         q = bowler.Query(config_dir)
-        m(q).execute(interactive=args.interactive, write=True)
+        m(q).execute(interactive=not args.yes, write=True)
 
     changed = False
     for py, backup in file_and_backup(config_dir):
@@ -193,8 +193,8 @@
         help="Use the specified configuration file (migrates every .py file in this directory)",
     )
     parser.add_argument(
-        "--interactive",
+        "--yes",
         action="store_true",
-        help="Interactively apply diff (similar to git add -p)",
+        help="Automatically apply diffs with no confirmation",
     )
     parser.set_defaults(func=do_migrate)

{"golden_diff": "diff --git a/libqtile/scripts/migrate.py b/libqtile/scripts/migrate.py\n--- a/libqtile/scripts/migrate.py\n+++ b/libqtile/scripts/migrate.py\n@@ -162,7 +162,7 @@\n \n     for m in MIGRATIONS:\n         q = bowler.Query(config_dir)\n-        m(q).execute(interactive=args.interactive, write=True)\n+        m(q).execute(interactive=not args.yes, write=True)\n \n     changed = False\n     for py, backup in file_and_backup(config_dir):\n@@ -193,8 +193,8 @@\n         help=\"Use the specified configuration file (migrates every .py file in this directory)\",\n     )\n     parser.add_argument(\n-        \"--interactive\",\n+        \"--yes\",\n         action=\"store_true\",\n-        help=\"Interactively apply diff (similar to git add -p)\",\n+        help=\"Automatically apply diffs with no confirmation\",\n     )\n     parser.set_defaults(func=do_migrate)\n", "issue": "migrations need version annotations\nWe have cases where migrations are non-idempotent, so we should only run them when upgrading from a particular version to a different one.\r\n\r\nMy suggestion is to write a `qtile_version = \"$whatever` into users' configs after they've done a migration, and then we can parse that and only run migrations newer than that.\r\n\r\nWe'll need to annotate the migrations we have somehow, but we can do that reasonably easily (a decorator, make them callable classes, whatever).\r\n\r\nThoughts?\n", "before_files": [{"content": "# Copyright (c) 2021, Tycho Andersen. All rights reserved.\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nimport filecmp\nimport os\nimport os.path\nimport shutil\nimport sys\nfrom glob import glob\n\nBACKUP_SUFFIX = \".migrate.bak\"\n\ntry:\n    import bowler\nexcept ImportError:\n    pass\n\n\ndef rename_hook(query, fro, to):\n    # could match on dotted_name< 'hook' '.' 'subscribe' '.' '{name}' >\n    # but the replacement gets more complicated...\n    selector = \"'{name}'\".format(name=fro)\n    q = query.select_pattern(selector)\n    q.current.kwargs[\"name\"] = fro\n    return q.rename(to)\n\n\ndef client_name_updated(query):\n    \"\"\" Rename window_name_change -> client_name_updated\"\"\"\n    return rename_hook(query, \"window_name_change\", \"client_name_updated\")\n\n\ndef tile_master_windows_rename(query):\n    return (\n        query\n        .select_function(\"Tile\")\n        .modify_argument(\"masterWindows\", \"master_length\")\n    )\n\n\ndef threaded_poll_text_rename(query):\n    return (\n        query\n        .select_class(\"ThreadedPollText\")\n        .rename(\"ThreadPoolText\")\n    )\n\n\ndef pacman_to_checkupdates(query):\n    return (\n        query\n        .select_class(\"Pacman\")\n        .rename(\"CheckUpdates\")\n    )\n\n\ndef hook_main_function(query):\n    def modify_main(node, capture, filename):\n        main = capture.get(\"function_def\")\n        if main.prev_sibling:\n            for leaf in main.prev_sibling.leaves():\n                if \"startup\" == leaf.value:\n                    return\n        args = capture.get(\"function_arguments\")\n        if args:\n            args[0].remove()\n            main.prefix += \"from libqtile import hook, qtile\\n\"\n            main.prefix += \"@hook.subscribe.startup\\n\"\n\n    return (\n        query\n        .select_function(\"main\")\n        .is_def()\n        .modify(modify_main)\n    )\n\n\n# Deprecated new_at_current key replaced by new_client_position.\n# In the node, we want to change the key name\n# and adapts its value depending of the previous value :\n#   new_at_current=True => new_client_position=before_current\n#   new_at_current<>True => new_client_position=after_current\ndef update_node_nac(node, capture, filename):\n    key = capture.get(\"k\")\n    key.value = \"new_client_position\"\n    val = capture.get(\"v\")\n    if val.value == \"True\":\n        val.value = \"'before_current'\"\n    else:\n        val.value = \"'after_current'\"\n\n\ndef new_at_current_to_new_client_position(query):\n    old_pattern = \"\"\"\n        argument< k=\"new_at_current\" \"=\" v=any >\n    \"\"\"\n    return (\n        query\n        .select(old_pattern)\n        .modify(update_node_nac)\n    )\n\n\nMIGRATIONS = [\n    client_name_updated,\n    tile_master_windows_rename,\n    threaded_poll_text_rename,\n    pacman_to_checkupdates,\n    hook_main_function,\n    new_at_current_to_new_client_position,\n]\n\n\nMODULE_RENAMES = [\n    (\"libqtile.command_graph\", \"libqtile.command.graph\"),\n    (\"libqtile.command_client\", \"libqtile.command.client\"),\n    (\"libqtile.command_interface\", \"libqtile.command.interface\"),\n    (\"libqtile.command_object\", \"libqtile.command.base\"),\n    (\"libqtile.window\", \"libqtile.backend.x11.window\"),\n]\n\nfor (fro, to) in MODULE_RENAMES:\n    def f(query, fro=fro, to=to):\n        return (\n            query\n            .select_module(fro)\n            .rename(to)\n        )\n    MIGRATIONS.append(f)\n\n\ndef file_and_backup(config_dir):\n    for py in glob(os.path.join(config_dir, \"*.py\")):\n        backup = py + BACKUP_SUFFIX\n        yield py, backup\n\n\ndef do_migrate(args):\n    if \"bowler\" not in sys.modules:\n        print(\"bowler can't be found, not migrating config file\")\n        print(\"install it and try again\")\n        sys.exit(1)\n\n    config_dir = os.path.dirname(args.config)\n    for py, backup in file_and_backup(config_dir):\n        shutil.copyfile(py, backup)\n\n    for m in MIGRATIONS:\n        q = bowler.Query(config_dir)\n        m(q).execute(interactive=args.interactive, write=True)\n\n    changed = False\n    for py, backup in file_and_backup(config_dir):\n        backup = py + BACKUP_SUFFIX\n        if not filecmp.cmp(py, backup, shallow=False):\n            changed = True\n            break\n\n    if not changed:\n        print(\"Config unchanged.\")\n        for _, backup in file_and_backup(config_dir):\n            os.remove(backup)\n\n\ndef add_subcommand(subparsers, parents):\n    parser = subparsers.add_parser(\n        \"migrate\",\n        parents=parents,\n        help=\"Migrate a configuration file to the current API\"\n    )\n    parser.add_argument(\n        \"-c\",\n        \"--config\",\n        action=\"store\",\n        default=os.path.expanduser(\n            os.path.join(os.getenv(\"XDG_CONFIG_HOME\", \"~/.config\"), \"qtile\", \"config.py\")\n        ),\n        help=\"Use the specified configuration file (migrates every .py file in this directory)\",\n    )\n    parser.add_argument(\n        \"--interactive\",\n        action=\"store_true\",\n        help=\"Interactively apply diff (similar to git add -p)\",\n    )\n    parser.set_defaults(func=do_migrate)\n", "path": "libqtile/scripts/migrate.py"}], "after_files": [{"content": "# Copyright (c) 2021, Tycho Andersen. All rights reserved.\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nimport filecmp\nimport os\nimport os.path\nimport shutil\nimport sys\nfrom glob import glob\n\nBACKUP_SUFFIX = \".migrate.bak\"\n\ntry:\n    import bowler\nexcept ImportError:\n    pass\n\n\ndef rename_hook(query, fro, to):\n    # could match on dotted_name< 'hook' '.' 'subscribe' '.' '{name}' >\n    # but the replacement gets more complicated...\n    selector = \"'{name}'\".format(name=fro)\n    q = query.select_pattern(selector)\n    q.current.kwargs[\"name\"] = fro\n    return q.rename(to)\n\n\ndef client_name_updated(query):\n    \"\"\" Rename window_name_change -> client_name_updated\"\"\"\n    return rename_hook(query, \"window_name_change\", \"client_name_updated\")\n\n\ndef tile_master_windows_rename(query):\n    return (\n        query\n        .select_function(\"Tile\")\n        .modify_argument(\"masterWindows\", \"master_length\")\n    )\n\n\ndef threaded_poll_text_rename(query):\n    return (\n        query\n        .select_class(\"ThreadedPollText\")\n        .rename(\"ThreadPoolText\")\n    )\n\n\ndef pacman_to_checkupdates(query):\n    return (\n        query\n        .select_class(\"Pacman\")\n        .rename(\"CheckUpdates\")\n    )\n\n\ndef hook_main_function(query):\n    def modify_main(node, capture, filename):\n        main = capture.get(\"function_def\")\n        if main.prev_sibling:\n            for leaf in main.prev_sibling.leaves():\n                if \"startup\" == leaf.value:\n                    return\n        args = capture.get(\"function_arguments\")\n        if args:\n            args[0].remove()\n            main.prefix += \"from libqtile import hook, qtile\\n\"\n            main.prefix += \"@hook.subscribe.startup\\n\"\n\n    return (\n        query\n        .select_function(\"main\")\n        .is_def()\n        .modify(modify_main)\n    )\n\n\n# Deprecated new_at_current key replaced by new_client_position.\n# In the node, we want to change the key name\n# and adapts its value depending of the previous value :\n#   new_at_current=True => new_client_position=before_current\n#   new_at_current<>True => new_client_position=after_current\ndef update_node_nac(node, capture, filename):\n    key = capture.get(\"k\")\n    key.value = \"new_client_position\"\n    val = capture.get(\"v\")\n    if val.value == \"True\":\n        val.value = \"'before_current'\"\n    else:\n        val.value = \"'after_current'\"\n\n\ndef new_at_current_to_new_client_position(query):\n    old_pattern = \"\"\"\n        argument< k=\"new_at_current\" \"=\" v=any >\n    \"\"\"\n    return (\n        query\n        .select(old_pattern)\n        .modify(update_node_nac)\n    )\n\n\nMIGRATIONS = [\n    client_name_updated,\n    tile_master_windows_rename,\n    threaded_poll_text_rename,\n    pacman_to_checkupdates,\n    hook_main_function,\n    new_at_current_to_new_client_position,\n]\n\n\nMODULE_RENAMES = [\n    (\"libqtile.command_graph\", \"libqtile.command.graph\"),\n    (\"libqtile.command_client\", \"libqtile.command.client\"),\n    (\"libqtile.command_interface\", \"libqtile.command.interface\"),\n    (\"libqtile.command_object\", \"libqtile.command.base\"),\n    (\"libqtile.window\", \"libqtile.backend.x11.window\"),\n]\n\nfor (fro, to) in MODULE_RENAMES:\n    def f(query, fro=fro, to=to):\n        return (\n            query\n            .select_module(fro)\n            .rename(to)\n        )\n    MIGRATIONS.append(f)\n\n\ndef file_and_backup(config_dir):\n    for py in glob(os.path.join(config_dir, \"*.py\")):\n        backup = py + BACKUP_SUFFIX\n        yield py, backup\n\n\ndef do_migrate(args):\n    if \"bowler\" not in sys.modules:\n        print(\"bowler can't be found, not migrating config file\")\n        print(\"install it and try again\")\n        sys.exit(1)\n\n    config_dir = os.path.dirname(args.config)\n    for py, backup in file_and_backup(config_dir):\n        shutil.copyfile(py, backup)\n\n    for m in MIGRATIONS:\n        q = bowler.Query(config_dir)\n        m(q).execute(interactive=not args.yes, write=True)\n\n    changed = False\n    for py, backup in file_and_backup(config_dir):\n        backup = py + BACKUP_SUFFIX\n        if not filecmp.cmp(py, backup, shallow=False):\n            changed = True\n            break\n\n    if not changed:\n        print(\"Config unchanged.\")\n        for _, backup in file_and_backup(config_dir):\n            os.remove(backup)\n\n\ndef add_subcommand(subparsers, parents):\n    parser = subparsers.add_parser(\n        \"migrate\",\n        parents=parents,\n        help=\"Migrate a configuration file to the current API\"\n    )\n    parser.add_argument(\n        \"-c\",\n        \"--config\",\n        action=\"store\",\n        default=os.path.expanduser(\n            os.path.join(os.getenv(\"XDG_CONFIG_HOME\", \"~/.config\"), \"qtile\", \"config.py\")\n        ),\n        help=\"Use the specified configuration file (migrates every .py file in this directory)\",\n    )\n    parser.add_argument(\n        \"--yes\",\n        action=\"store_true\",\n        help=\"Automatically apply diffs with no confirmation\",\n    )\n    parser.set_defaults(func=do_migrate)\n", "path": "libqtile/scripts/migrate.py"}]}