pyinfra 0.11.dev3__py3-none-any.whl → 3.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +19 -3
  4. pyinfra/api/arguments.py +413 -0
  5. pyinfra/api/arguments_typed.py +79 -0
  6. pyinfra/api/command.py +274 -0
  7. pyinfra/api/config.py +222 -28
  8. pyinfra/api/connect.py +33 -13
  9. pyinfra/api/connectors.py +27 -0
  10. pyinfra/api/deploy.py +65 -66
  11. pyinfra/api/exceptions.py +73 -18
  12. pyinfra/api/facts.py +267 -200
  13. pyinfra/api/host.py +416 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/metadata.py +69 -0
  16. pyinfra/api/operation.py +432 -262
  17. pyinfra/api/operations.py +273 -260
  18. pyinfra/api/state.py +302 -248
  19. pyinfra/api/util.py +309 -369
  20. pyinfra/connectors/base.py +173 -0
  21. pyinfra/connectors/chroot.py +212 -0
  22. pyinfra/connectors/docker.py +405 -0
  23. pyinfra/connectors/dockerssh.py +297 -0
  24. pyinfra/connectors/local.py +238 -0
  25. pyinfra/connectors/scp/__init__.py +1 -0
  26. pyinfra/connectors/scp/client.py +204 -0
  27. pyinfra/connectors/ssh.py +727 -0
  28. pyinfra/connectors/ssh_util.py +114 -0
  29. pyinfra/connectors/sshuserclient/client.py +309 -0
  30. pyinfra/connectors/sshuserclient/config.py +102 -0
  31. pyinfra/connectors/terraform.py +135 -0
  32. pyinfra/connectors/util.py +417 -0
  33. pyinfra/connectors/vagrant.py +183 -0
  34. pyinfra/context.py +145 -0
  35. pyinfra/facts/__init__.py +7 -6
  36. pyinfra/facts/apk.py +22 -7
  37. pyinfra/facts/apt.py +117 -60
  38. pyinfra/facts/brew.py +100 -15
  39. pyinfra/facts/bsdinit.py +23 -0
  40. pyinfra/facts/cargo.py +37 -0
  41. pyinfra/facts/choco.py +47 -0
  42. pyinfra/facts/crontab.py +195 -0
  43. pyinfra/facts/deb.py +94 -0
  44. pyinfra/facts/dnf.py +48 -0
  45. pyinfra/facts/docker.py +96 -23
  46. pyinfra/facts/efibootmgr.py +113 -0
  47. pyinfra/facts/files.py +629 -58
  48. pyinfra/facts/flatpak.py +77 -0
  49. pyinfra/facts/freebsd.py +70 -0
  50. pyinfra/facts/gem.py +19 -6
  51. pyinfra/facts/git.py +59 -14
  52. pyinfra/facts/gpg.py +150 -0
  53. pyinfra/facts/hardware.py +313 -167
  54. pyinfra/facts/iptables.py +72 -62
  55. pyinfra/facts/launchd.py +44 -0
  56. pyinfra/facts/lxd.py +17 -4
  57. pyinfra/facts/mysql.py +122 -86
  58. pyinfra/facts/npm.py +17 -9
  59. pyinfra/facts/openrc.py +71 -0
  60. pyinfra/facts/opkg.py +246 -0
  61. pyinfra/facts/pacman.py +50 -7
  62. pyinfra/facts/pip.py +24 -7
  63. pyinfra/facts/pipx.py +82 -0
  64. pyinfra/facts/pkg.py +15 -6
  65. pyinfra/facts/pkgin.py +35 -0
  66. pyinfra/facts/podman.py +54 -0
  67. pyinfra/facts/postgres.py +178 -0
  68. pyinfra/facts/postgresql.py +6 -147
  69. pyinfra/facts/rpm.py +105 -0
  70. pyinfra/facts/runit.py +77 -0
  71. pyinfra/facts/selinux.py +161 -0
  72. pyinfra/facts/server.py +762 -285
  73. pyinfra/facts/snap.py +88 -0
  74. pyinfra/facts/systemd.py +139 -0
  75. pyinfra/facts/sysvinit.py +59 -0
  76. pyinfra/facts/upstart.py +35 -0
  77. pyinfra/facts/util/__init__.py +17 -0
  78. pyinfra/facts/util/databases.py +4 -6
  79. pyinfra/facts/util/packaging.py +37 -6
  80. pyinfra/facts/util/units.py +30 -0
  81. pyinfra/facts/util/win_files.py +99 -0
  82. pyinfra/facts/vzctl.py +20 -13
  83. pyinfra/facts/xbps.py +35 -0
  84. pyinfra/facts/yum.py +34 -40
  85. pyinfra/facts/zfs.py +77 -0
  86. pyinfra/facts/zypper.py +42 -0
  87. pyinfra/local.py +45 -83
  88. pyinfra/operations/__init__.py +12 -0
  89. pyinfra/operations/apk.py +99 -0
  90. pyinfra/operations/apt.py +496 -0
  91. pyinfra/operations/brew.py +232 -0
  92. pyinfra/operations/bsdinit.py +59 -0
  93. pyinfra/operations/cargo.py +45 -0
  94. pyinfra/operations/choco.py +61 -0
  95. pyinfra/operations/crontab.py +194 -0
  96. pyinfra/operations/dnf.py +213 -0
  97. pyinfra/operations/docker.py +492 -0
  98. pyinfra/operations/files.py +2014 -0
  99. pyinfra/operations/flatpak.py +95 -0
  100. pyinfra/operations/freebsd/__init__.py +12 -0
  101. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  102. pyinfra/operations/freebsd/pkg.py +219 -0
  103. pyinfra/operations/freebsd/service.py +116 -0
  104. pyinfra/operations/freebsd/sysrc.py +92 -0
  105. pyinfra/operations/gem.py +48 -0
  106. pyinfra/operations/git.py +420 -0
  107. pyinfra/operations/iptables.py +312 -0
  108. pyinfra/operations/launchd.py +45 -0
  109. pyinfra/operations/lxd.py +69 -0
  110. pyinfra/operations/mysql.py +610 -0
  111. pyinfra/operations/npm.py +57 -0
  112. pyinfra/operations/openrc.py +63 -0
  113. pyinfra/operations/opkg.py +89 -0
  114. pyinfra/operations/pacman.py +82 -0
  115. pyinfra/operations/pip.py +206 -0
  116. pyinfra/operations/pipx.py +103 -0
  117. pyinfra/operations/pkg.py +71 -0
  118. pyinfra/operations/pkgin.py +92 -0
  119. pyinfra/operations/postgres.py +437 -0
  120. pyinfra/operations/postgresql.py +30 -0
  121. pyinfra/operations/puppet.py +41 -0
  122. pyinfra/operations/python.py +73 -0
  123. pyinfra/operations/runit.py +184 -0
  124. pyinfra/operations/selinux.py +190 -0
  125. pyinfra/operations/server.py +1100 -0
  126. pyinfra/operations/snap.py +118 -0
  127. pyinfra/operations/ssh.py +217 -0
  128. pyinfra/operations/systemd.py +150 -0
  129. pyinfra/operations/sysvinit.py +142 -0
  130. pyinfra/operations/upstart.py +68 -0
  131. pyinfra/operations/util/__init__.py +12 -0
  132. pyinfra/operations/util/docker.py +407 -0
  133. pyinfra/operations/util/files.py +247 -0
  134. pyinfra/operations/util/packaging.py +338 -0
  135. pyinfra/operations/util/service.py +46 -0
  136. pyinfra/operations/vzctl.py +137 -0
  137. pyinfra/operations/xbps.py +78 -0
  138. pyinfra/operations/yum.py +213 -0
  139. pyinfra/operations/zfs.py +176 -0
  140. pyinfra/operations/zypper.py +193 -0
  141. pyinfra/progress.py +44 -32
  142. pyinfra/py.typed +0 -0
  143. pyinfra/version.py +9 -1
  144. pyinfra-3.6.dist-info/METADATA +142 -0
  145. pyinfra-3.6.dist-info/RECORD +160 -0
  146. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info}/WHEEL +1 -2
  147. pyinfra-3.6.dist-info/entry_points.txt +12 -0
  148. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info/licenses}/LICENSE.md +1 -1
  149. pyinfra_cli/__init__.py +1 -0
  150. pyinfra_cli/cli.py +793 -0
  151. pyinfra_cli/commands.py +66 -0
  152. pyinfra_cli/exceptions.py +155 -65
  153. pyinfra_cli/inventory.py +233 -89
  154. pyinfra_cli/log.py +39 -43
  155. pyinfra_cli/main.py +26 -495
  156. pyinfra_cli/prints.py +215 -156
  157. pyinfra_cli/util.py +172 -105
  158. pyinfra_cli/virtualenv.py +25 -20
  159. pyinfra/api/connectors/__init__.py +0 -21
  160. pyinfra/api/connectors/ansible.py +0 -99
  161. pyinfra/api/connectors/docker.py +0 -178
  162. pyinfra/api/connectors/local.py +0 -169
  163. pyinfra/api/connectors/ssh.py +0 -402
  164. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  165. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  166. pyinfra/api/connectors/util.py +0 -63
  167. pyinfra/api/connectors/vagrant.py +0 -155
  168. pyinfra/facts/init.py +0 -176
  169. pyinfra/facts/util/files.py +0 -102
  170. pyinfra/hook.py +0 -41
  171. pyinfra/modules/__init__.py +0 -11
  172. pyinfra/modules/apk.py +0 -64
  173. pyinfra/modules/apt.py +0 -272
  174. pyinfra/modules/brew.py +0 -122
  175. pyinfra/modules/files.py +0 -711
  176. pyinfra/modules/gem.py +0 -30
  177. pyinfra/modules/git.py +0 -115
  178. pyinfra/modules/init.py +0 -344
  179. pyinfra/modules/iptables.py +0 -271
  180. pyinfra/modules/lxd.py +0 -45
  181. pyinfra/modules/mysql.py +0 -347
  182. pyinfra/modules/npm.py +0 -47
  183. pyinfra/modules/pacman.py +0 -60
  184. pyinfra/modules/pip.py +0 -99
  185. pyinfra/modules/pkg.py +0 -43
  186. pyinfra/modules/postgresql.py +0 -245
  187. pyinfra/modules/puppet.py +0 -20
  188. pyinfra/modules/python.py +0 -37
  189. pyinfra/modules/server.py +0 -524
  190. pyinfra/modules/ssh.py +0 -150
  191. pyinfra/modules/util/files.py +0 -52
  192. pyinfra/modules/util/packaging.py +0 -118
  193. pyinfra/modules/vzctl.py +0 -133
  194. pyinfra/modules/yum.py +0 -171
  195. pyinfra/pseudo_modules.py +0 -64
  196. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  197. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  198. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  199. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  200. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  201. pyinfra_cli/__main__.py +0 -40
  202. pyinfra_cli/config.py +0 -92
  203. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  204. /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
@@ -0,0 +1,420 @@
1
+ """
2
+ Manage git repositories and configuration.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import re
8
+
9
+ from pyinfra import host
10
+ from pyinfra.api import OperationError, operation
11
+ from pyinfra.facts.files import Directory, File
12
+ from pyinfra.facts.git import GitBranch, GitConfig, GitTag, GitTrackingBranch
13
+
14
+ from . import files, ssh
15
+ from .util.files import chown, unix_path_join
16
+
17
+
18
+ @operation()
19
+ def config(key: str, value: str, multi_value=False, repo: str | None = None, system=False):
20
+ """
21
+ Manage git config at repository, user or system level.
22
+
23
+ + key: the key of the config to ensure
24
+ + value: the value this key should have
25
+ + multi_value: Add the value rather than set it for settings that can have multiple values
26
+ + repo: specify the git repo path to edit local config (defaults to global)
27
+ + system: whether, when ``repo`` is unspecified, to work at system level (or default to global)
28
+
29
+ **Examples:**
30
+
31
+ .. code:: python
32
+
33
+ from pyinfra.operations import git
34
+ git.config(
35
+ name="Always prune specified repo",
36
+ key="fetch.prune",
37
+ value="true",
38
+ repo="/usr/local/src/pyinfra",
39
+ )
40
+
41
+ git.config(
42
+ name="Ensure user name is set for all repos of specified user",
43
+ key="user.name",
44
+ value="Anon E. Mouse",
45
+ _sudo=True,
46
+ _sudo_user="anon"
47
+ )
48
+
49
+ git.config(
50
+ name="Ensure same date format for all users",
51
+ key="log.date",
52
+ value="iso",
53
+ system=True
54
+ )
55
+
56
+ """
57
+
58
+ existing_config = {}
59
+
60
+ if not repo:
61
+ existing_config = host.get_fact(GitConfig, system=system)
62
+
63
+ # Only get the config if the repo exists at this stage
64
+ elif host.get_fact(Directory, path=unix_path_join(repo, ".git")):
65
+ existing_config = host.get_fact(GitConfig, repo=repo)
66
+
67
+ if repo is None:
68
+ base_command = "git config" + (" --system" if system else " --global")
69
+ else:
70
+ base_command = "cd {0} && git config --local".format(repo)
71
+
72
+ if not multi_value and existing_config.get(key) != [value]:
73
+ yield '{0} {1} "{2}"'.format(base_command, key, value)
74
+
75
+ elif multi_value and value not in existing_config.get(key, []):
76
+ yield '{0} --add {1} "{2}"'.format(base_command, key, value)
77
+
78
+ else:
79
+ host.noop("git config {0} is set to {1}".format(key, value))
80
+
81
+
82
+ @operation()
83
+ def repo(
84
+ src: str,
85
+ dest: str,
86
+ branch: str | None = None,
87
+ pull=True,
88
+ rebase=False,
89
+ user: str | None = None,
90
+ group: str | None = None,
91
+ ssh_keyscan=False,
92
+ update_submodules=False,
93
+ recursive_submodules=False,
94
+ ):
95
+ """
96
+ Clone/pull git repositories.
97
+
98
+ + src: the git source URL
99
+ + dest: directory to clone to
100
+ + branch: branch to pull/checkout
101
+ + pull: pull any changes for the branch
102
+ + rebase: when pulling, use ``--rebase``
103
+ + user: chown files to this user after
104
+ + group: chown files to this group after
105
+ + ssh_keyscan: keyscan the remote host if not in known_hosts before clone/pull
106
+ + update_submodules: update any git submodules
107
+ + recursive_submodules: update git submodules recursively
108
+
109
+ **Example:**
110
+
111
+ .. code:: python
112
+
113
+ git.repo(
114
+ name="Clone repo",
115
+ src="https://github.com/Fizzadar/pyinfra.git",
116
+ dest="/usr/local/src/pyinfra",
117
+ )
118
+ """
119
+
120
+ # Ensure our target directory exists
121
+ yield from files.directory._inner(dest)
122
+
123
+ # Do we need to scan for the remote host key?
124
+ if ssh_keyscan:
125
+ # Attempt to parse the domain from the git repository
126
+ domain = re.match(r"^[a-zA-Z0-9]+@([0-9a-zA-Z\.\-]+)", src)
127
+
128
+ if domain:
129
+ yield from ssh.keyscan._inner(domain.group(1))
130
+ else:
131
+ raise OperationError(
132
+ "Could not parse domain (to SSH keyscan) from: {0}".format(src),
133
+ )
134
+
135
+ # Store git commands for directory prefix
136
+ git_commands = []
137
+ git_dir = unix_path_join(dest, ".git")
138
+ is_repo = host.get_fact(Directory, path=git_dir)
139
+
140
+ # Cloning new repo?
141
+ if not is_repo:
142
+ if branch:
143
+ git_commands.append("clone {0} --branch {1} .".format(src, branch))
144
+ else:
145
+ git_commands.append("clone {0} .".format(src))
146
+ # Ensuring existing repo
147
+ else:
148
+ is_tag = False
149
+ if branch and host.get_fact(GitBranch, repo=dest) != branch:
150
+ git_commands.append("fetch") # fetch to ensure we have the branch locally
151
+ git_commands.append("checkout {0}".format(branch))
152
+ if branch and branch in (host.get_fact(GitTag, repo=dest) or []):
153
+ git_commands.append("checkout {0}".format(branch))
154
+ is_tag = True
155
+ if pull and not is_tag:
156
+ if rebase:
157
+ git_commands.append("pull --rebase")
158
+ else:
159
+ git_commands.append("pull")
160
+
161
+ if update_submodules:
162
+ if recursive_submodules:
163
+ git_commands.append("submodule update --init --recursive")
164
+ else:
165
+ git_commands.append("submodule update --init")
166
+
167
+ # Attach prefixes for directory
168
+ command_prefix = "cd {0} && git".format(dest)
169
+ git_commands = ["{0} {1}".format(command_prefix, command) for command in git_commands]
170
+
171
+ for cmd in git_commands:
172
+ yield cmd
173
+
174
+ # Apply any user or group if we did anything
175
+ if git_commands and (user or group):
176
+ yield chown(dest, user, group, recursive=True)
177
+
178
+
179
+ @operation()
180
+ def worktree(
181
+ worktree: str,
182
+ repo: str | None = None,
183
+ detached=False,
184
+ new_branch: str | None = None,
185
+ commitish: str | None = None,
186
+ pull=True,
187
+ rebase=False,
188
+ from_remote_branch: tuple[str, str] | None = None,
189
+ present=True,
190
+ assume_repo_exists=False,
191
+ force=False,
192
+ user: str | None = None,
193
+ group: str | None = None,
194
+ ):
195
+ """
196
+ Manage git worktrees.
197
+
198
+ + worktree: git working tree directory
199
+ + repo: git main repository directory
200
+ + detached: create a working tree with a detached HEAD
201
+ + new_branch: local branch name created at the same time than the worktree
202
+ + commitish: from which git commit, branch, ... the worktree is created
203
+ + pull: pull any changes from a remote branch if set
204
+ + rebase: when pulling, use ``--rebase``
205
+ + from_remote_branch: a 2-tuple ``(remote, branch)`` that identifies a remote branch
206
+ + present: whether the working tree should exist
207
+ + assume_repo_exists: whether to assume the main repo exists
208
+ + force: whether to use ``--force`` when adding/removing worktrees
209
+ + user: chown files to this user after
210
+ + group: chown files to this group after
211
+
212
+ **Examples:**
213
+
214
+ .. code:: python
215
+
216
+ git.worktree(
217
+ name="Create a worktree from the current repo `HEAD`",
218
+ repo="/usr/local/src/pyinfra/master",
219
+ worktree="/usr/local/src/pyinfra/hotfix"
220
+ )
221
+
222
+ git.worktree(
223
+ name="Create a worktree from the commit `4e091aa0`",
224
+ repo="/usr/local/src/pyinfra/master",
225
+ worktree="/usr/local/src/pyinfra/hotfix",
226
+ commitish="4e091aa0"
227
+ )
228
+
229
+ git.worktree(
230
+ name="Create a worktree from the tag `4e091aa0`, even if already registered",
231
+ repo="/usr/local/src/pyinfra/master",
232
+ worktree="/usr/local/src/pyinfra/2.x",
233
+ commitish="2.x",
234
+ force=True
235
+ )
236
+
237
+ git.worktree(
238
+ name="Create a worktree with a new local branch `v1.0`",
239
+ repo="/usr/local/src/pyinfra/master",
240
+ worktree="/usr/local/src/pyinfra/hotfix",
241
+ new_branch="v1.0",
242
+ )
243
+
244
+ git.worktree(
245
+ name="Create a worktree from the commit 4e091aa0 with the new local branch `v1.0`",
246
+ repo="/usr/local/src/pyinfra/master",
247
+ worktree="/usr/local/src/pyinfra/hotfix",
248
+ new_branch="v1.0",
249
+ commitish="4e091aa0"
250
+ )
251
+
252
+ git.worktree(
253
+ name="Create a worktree with a detached `HEAD`",
254
+ repo="/usr/local/src/pyinfra/master",
255
+ worktree="/usr/local/src/pyinfra/hotfix",
256
+ detached=True,
257
+ )
258
+
259
+ git.worktree(
260
+ name="Create a worktree with a detached `HEAD` from commit `4e091aa0`",
261
+ repo="/usr/local/src/pyinfra/master",
262
+ worktree="/usr/local/src/pyinfra/hotfix",
263
+ commitish="4e091aa0",
264
+ detached=True,
265
+ )
266
+
267
+ git.worktree(
268
+ name="Create a worktree from the existing local branch `v1.0`",
269
+ repo="/usr/local/src/pyinfra/master",
270
+ worktree="/usr/local/src/pyinfra/hotfix",
271
+ commitish="v1.0"
272
+ )
273
+
274
+ git.worktree(
275
+ name="Create a worktree with a new branch `v1.0` that tracks `origin/v1.0`",
276
+ repo="/usr/local/src/pyinfra/master",
277
+ worktree="/usr/local/src/pyinfra/hotfix",
278
+ new_branch="v1.0",
279
+ commitish="v1.0"
280
+ )
281
+
282
+ git.worktree(
283
+ name="Idempotent worktree creation, never pulls",
284
+ repo="/usr/local/src/pyinfra/master",
285
+ worktree="/usr/local/src/pyinfra/hotfix",
286
+ new_branch="v1.0",
287
+ commitish="v1.0",
288
+ pull=False
289
+ )
290
+
291
+ git.worktree(
292
+ name="Pull an existing worktree already linked to a tracking branch",
293
+ repo="/usr/local/src/pyinfra/master",
294
+ worktree="/usr/local/src/pyinfra/hotfix"
295
+ )
296
+
297
+ git.worktree(
298
+ name="Pull an existing worktree from a specific remote branch",
299
+ repo="/usr/local/src/pyinfra/master",
300
+ worktree="/usr/local/src/pyinfra/hotfix",
301
+ from_remote_branch=("origin", "master")
302
+ )
303
+
304
+ git.worktree(
305
+ name="Remove a worktree",
306
+ worktree="/usr/local/src/pyinfra/hotfix",
307
+ present=False,
308
+ )
309
+
310
+ git.worktree(
311
+ name="Remove an unclean worktree",
312
+ worktree="/usr/local/src/pyinfra/hotfix",
313
+ present=False,
314
+ force=True,
315
+ )
316
+ """
317
+
318
+ # Doesn't exist & we want it
319
+ if not host.get_fact(Directory, path=worktree) and present:
320
+ # be sure that `repo` is a GIT repository
321
+ if not assume_repo_exists and not host.get_fact(
322
+ Directory,
323
+ path=unix_path_join(repo, ".git"),
324
+ ):
325
+ raise OperationError(
326
+ "The following folder is not a valid GIT repository : {0}".format(repo),
327
+ )
328
+
329
+ command_parts = ["cd {0} && git worktree add".format(repo)]
330
+
331
+ if new_branch:
332
+ command_parts.append("-b {0}".format(new_branch))
333
+ elif detached:
334
+ command_parts.append("--detach")
335
+
336
+ if force:
337
+ command_parts.append("--force")
338
+
339
+ command_parts.append(worktree)
340
+
341
+ if commitish:
342
+ command_parts.append(commitish)
343
+
344
+ yield " ".join(command_parts)
345
+
346
+ # Apply any user or group
347
+ if user or group:
348
+ yield chown(worktree, user, group, recursive=True)
349
+
350
+ # It exists and we don't want it
351
+ elif host.get_fact(Directory, path=worktree) and not present:
352
+ command = "cd {0} && git worktree remove .".format(worktree)
353
+
354
+ if force:
355
+ command += " --force"
356
+
357
+ yield command
358
+
359
+ # It exists and we still want it => pull/rebase it
360
+ elif host.get_fact(Directory, path=worktree) and present:
361
+ if not pull:
362
+ host.noop("Pull is disabled")
363
+
364
+ # pull the worktree only if it's already linked to a tracking branch or
365
+ # if a remote branch is set
366
+ elif host.get_fact(GitTrackingBranch, repo=worktree) or from_remote_branch:
367
+ command = "cd {0} && git pull".format(worktree)
368
+
369
+ if rebase:
370
+ command += " --rebase"
371
+
372
+ if from_remote_branch:
373
+ if len(from_remote_branch) != 2 or type(from_remote_branch) not in (tuple, list):
374
+ raise OperationError(
375
+ "The remote branch must be a 2-tuple (remote, branch) such as "
376
+ '("origin", "master")',
377
+ )
378
+ command += " {0} {1}".format(*from_remote_branch)
379
+
380
+ yield command
381
+
382
+
383
+ @operation()
384
+ def bare_repo(
385
+ path: str,
386
+ user: str | None = None,
387
+ group: str | None = None,
388
+ present=True,
389
+ ):
390
+ """
391
+ Create bare git repositories.
392
+
393
+ + path: path to the folder
394
+ + present: whether the bare repository should exist
395
+ + user: chown files to this user after
396
+ + group: chown files to this group after
397
+
398
+ **Example:**
399
+
400
+ .. code:: python
401
+
402
+ git.bare_repo(
403
+ name="Create bare repo",
404
+ path="/home/git/test.git",
405
+ )
406
+ """
407
+
408
+ yield from files.directory._inner(path, present=present)
409
+
410
+ if present:
411
+ head_filename = unix_path_join(path, "HEAD")
412
+ head_file = host.get_fact(File, path=head_filename)
413
+
414
+ if not head_file:
415
+ yield "git init --bare {0}".format(path)
416
+ if user or group:
417
+ yield chown(path, user, group, recursive=True)
418
+ else:
419
+ if (user and head_file["user"] != user) or (group and head_file["group"] != group):
420
+ yield chown(path, user, group, recursive=True)