batchfetch 1.3.5__tar.gz → 1.3.7__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: batchfetch
3
- Version: 1.3.5
3
+ Version: 1.3.7
4
4
  Summary: Efficiently clone and pull multiple Git repositories.
5
5
  Home-page: https://github.com/jamescherti/batchfetch
6
6
  Author: James Cherti
@@ -17,12 +17,26 @@ Classifier: Topic :: Utilities
17
17
  Requires-Python: >=3.6, <4
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
+ Requires-Dist: colorama
21
+ Requires-Dist: schema
22
+ Requires-Dist: setproctitle
23
+ Requires-Dist: PyYAML
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: license
30
+ Dynamic: license-file
31
+ Dynamic: requires-dist
32
+ Dynamic: requires-python
33
+ Dynamic: summary
20
34
 
21
35
  # Batchfetch - Efficiently clone or pull multiple Git repositories in parallel
22
36
 
23
37
  ## Introduction
24
38
 
25
- Batchfetch is a command-line tool designed to clone, fetch, and merge multiple Git repositories simultaneously. With Batchfetch, you no longer need to manually manage each repository one by one. It automates the tedious aspects of repository management, freeing you up to focus on what truly matters: your workflow.
39
+ Batchfetch is a command-line tool designed to clone, fetch, and merge multiple Git repositories simultaneously. With Batchfetch, you no longer need to manually manage each repository one by one. It automates the tedious aspects of repository management.
26
40
 
27
41
  But why use Batchfetch? Because it is extremely fast, cloning repositories quickly by running Git operations in parallel. It intelligently detects whether a `git fetch` is needed, further speeding up the process of downloading data from repositories. Additionally, it allows specifying the revision (for Git), ensuring that the cloned repository matches the exact version you require.
28
42
 
@@ -103,6 +117,7 @@ options:
103
117
  ```
104
118
 
105
119
  ## Features
120
+
106
121
  - Git Clone and Fetch/Merge: Clones the repositories and their submodules, ensuring that all the repositories are always up-to-date by fetching and merging changes.
107
122
  - Parallel Operations: Utilizes threads to simultaneously Git clone or pull multiple repositories, dramatically reducing wait times.
108
123
  - User-Friendly Interface: Provides simple and straightforward command-line options that make it easy to get started and effectively manage your repositories.
@@ -121,7 +136,7 @@ When *batchfetch* encounters an untracked file, it displays an error message to
121
136
 
122
137
  Here is an example of a *batchfetch.yaml* file that enables *batchfetch* to accept a list of untracked files:
123
138
 
124
- ``` yaml
139
+ ```yaml
125
140
  options:
126
141
  ignore_untracked:
127
142
  - ./test
@@ -147,7 +162,8 @@ Batchfetch is fast, not only because it runs Git commands in parallel, but also
147
162
  When the user has specifies a revision (branch or commit reference), Batchfetch only performs a `git fetch` if that revision does not exist locally. If the revision is already up to date, it simply proceeds to the next repository in the queue.
148
163
 
149
164
  That's why it is highly recommended to always specify the revision to speed up Batchfetch, if speed is important to you. Here is an example of a `batchfetch.yaml` file where the branch (`1.1.0`) or commit reference (`b9c6d9b6134b4981760893254f804a371ffbc899`) is specified:
150
- ``` yaml
165
+
166
+ ```yaml
151
167
  tasks:
152
168
  - git: https://github.com/jamescherti/outline-indent.el
153
169
  revision: "1.1.0"
@@ -157,12 +173,59 @@ tasks:
157
173
  revision: b9c6d9b6134b4981760893254f804a371ffbc899
158
174
  ```
159
175
 
176
+ ### How to configure additional Git remotes?
177
+
178
+ You can define additional remotes for a Git repository using the `remote` option. Pass a list of key-value pairs where the key is the remote name and the value is the corresponding URL. Batchfetch will automatically ensure these remotes are present and point to the correct URLs.
179
+
180
+ ```yaml
181
+ ---
182
+ tasks:
183
+ - git: https://github.com/jamescherti/easysession.el
184
+ path: emacs/easysession
185
+ remote:
186
+ upstream: https://github.com/name/repo
187
+ upstream2: https://github.com/name/repo2
188
+ ```
189
+
190
+ ### How to customize Git clone, merge, and update strategies?
191
+
192
+ Batchfetch allows you to define advanced options for interacting with Git repositories. These parameters can be declared globally under the `options` block to apply to all tasks, or individually within a specific task to override the global defaults.
193
+
194
+ * `git_clone_args`: A list of extra arguments to pass to `git clone`.
195
+ * `git_merge_args`: A list of extra arguments to pass to `git merge`.
196
+ * `git_update_strategy`: Specifies how updates should be applied. Valid options are `"merge"` (default), `"rebase"`, or `"reset"`.
197
+ * `git_pull`: A boolean indicating whether to run Git pull/fetch operations. Set to `false` to disable updating entirely (default: `true`).
198
+
199
+ Here is an example demonstrating both global and local configurations:
200
+
201
+ ```yaml
202
+ ---
203
+
204
+ options:
205
+ # Apply these defaults to all tasks
206
+ git_update_strategy: reset
207
+ git_clone_args:
208
+ - --recurse-submodules
209
+
210
+ tasks:
211
+ - git: https://github.com/jamescherti/compile-angel.el
212
+ git_pull: false # Do not attempt to update this repo
213
+
214
+ - git: https://github.com/jamescherti/outline-indent.el
215
+ # Override global strategy specifically for this task
216
+ git_update_strategy: merge
217
+ git_merge_args:
218
+ - --no-ff
219
+
220
+ ```
221
+
160
222
  ### How to execute a command before and after a task?
161
223
 
162
224
  To execute a command both before and after a specific task, you can define the `exec_before` and `exec_after` directives within the task configuration. These directives specify commands to be executed at the respective stages of the task lifecycle.
163
225
 
164
226
  Here is an example:
165
- ``` yaml
227
+
228
+ ```yaml
166
229
  ---
167
230
  tasks:
168
231
  - git: https://github.com/jamescherti/easysession.el
@@ -178,6 +241,7 @@ To configure `batchfetch` to handle a specific path, you can define your tasks i
178
241
  #### Example `batchfetch.yml` file:
179
242
 
180
243
  In the following example, the `easysession` task clones two Git repositories:
244
+
181
245
  ```yaml
182
246
  ---
183
247
  tasks:
@@ -213,7 +277,7 @@ batchfetch
213
277
 
214
278
  ## License
215
279
 
216
- Copyright (C) 2024-2025 [James Cherti](https://www.jamescherti.com)
280
+ Copyright (C) 2024-2026 [James Cherti](https://www.jamescherti.com)
217
281
 
218
282
  This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
219
283
 
@@ -1,28 +1,8 @@
1
- Metadata-Version: 2.1
2
- Name: batchfetch
3
- Version: 1.3.5
4
- Summary: Efficiently clone and pull multiple Git repositories.
5
- Home-page: https://github.com/jamescherti/batchfetch
6
- Author: James Cherti
7
- License: GPLv3
8
- Classifier: Development Status :: 5 - Production/Stable
9
- Classifier: License :: OSI Approved :: GNU General Public License (GPL)
10
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
11
- Classifier: Environment :: Console
12
- Classifier: Operating System :: POSIX :: Linux
13
- Classifier: Operating System :: POSIX :: Other
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Topic :: Software Development :: Version Control :: Git
16
- Classifier: Topic :: Utilities
17
- Requires-Python: >=3.6, <4
18
- Description-Content-Type: text/markdown
19
- License-File: LICENSE
20
-
21
1
  # Batchfetch - Efficiently clone or pull multiple Git repositories in parallel
22
2
 
23
3
  ## Introduction
24
4
 
25
- Batchfetch is a command-line tool designed to clone, fetch, and merge multiple Git repositories simultaneously. With Batchfetch, you no longer need to manually manage each repository one by one. It automates the tedious aspects of repository management, freeing you up to focus on what truly matters: your workflow.
5
+ Batchfetch is a command-line tool designed to clone, fetch, and merge multiple Git repositories simultaneously. With Batchfetch, you no longer need to manually manage each repository one by one. It automates the tedious aspects of repository management.
26
6
 
27
7
  But why use Batchfetch? Because it is extremely fast, cloning repositories quickly by running Git operations in parallel. It intelligently detects whether a `git fetch` is needed, further speeding up the process of downloading data from repositories. Additionally, it allows specifying the revision (for Git), ensuring that the cloned repository matches the exact version you require.
28
8
 
@@ -103,6 +83,7 @@ options:
103
83
  ```
104
84
 
105
85
  ## Features
86
+
106
87
  - Git Clone and Fetch/Merge: Clones the repositories and their submodules, ensuring that all the repositories are always up-to-date by fetching and merging changes.
107
88
  - Parallel Operations: Utilizes threads to simultaneously Git clone or pull multiple repositories, dramatically reducing wait times.
108
89
  - User-Friendly Interface: Provides simple and straightforward command-line options that make it easy to get started and effectively manage your repositories.
@@ -121,7 +102,7 @@ When *batchfetch* encounters an untracked file, it displays an error message to
121
102
 
122
103
  Here is an example of a *batchfetch.yaml* file that enables *batchfetch* to accept a list of untracked files:
123
104
 
124
- ``` yaml
105
+ ```yaml
125
106
  options:
126
107
  ignore_untracked:
127
108
  - ./test
@@ -147,7 +128,8 @@ Batchfetch is fast, not only because it runs Git commands in parallel, but also
147
128
  When the user has specifies a revision (branch or commit reference), Batchfetch only performs a `git fetch` if that revision does not exist locally. If the revision is already up to date, it simply proceeds to the next repository in the queue.
148
129
 
149
130
  That's why it is highly recommended to always specify the revision to speed up Batchfetch, if speed is important to you. Here is an example of a `batchfetch.yaml` file where the branch (`1.1.0`) or commit reference (`b9c6d9b6134b4981760893254f804a371ffbc899`) is specified:
150
- ``` yaml
131
+
132
+ ```yaml
151
133
  tasks:
152
134
  - git: https://github.com/jamescherti/outline-indent.el
153
135
  revision: "1.1.0"
@@ -157,12 +139,59 @@ tasks:
157
139
  revision: b9c6d9b6134b4981760893254f804a371ffbc899
158
140
  ```
159
141
 
142
+ ### How to configure additional Git remotes?
143
+
144
+ You can define additional remotes for a Git repository using the `remote` option. Pass a list of key-value pairs where the key is the remote name and the value is the corresponding URL. Batchfetch will automatically ensure these remotes are present and point to the correct URLs.
145
+
146
+ ```yaml
147
+ ---
148
+ tasks:
149
+ - git: https://github.com/jamescherti/easysession.el
150
+ path: emacs/easysession
151
+ remote:
152
+ upstream: https://github.com/name/repo
153
+ upstream2: https://github.com/name/repo2
154
+ ```
155
+
156
+ ### How to customize Git clone, merge, and update strategies?
157
+
158
+ Batchfetch allows you to define advanced options for interacting with Git repositories. These parameters can be declared globally under the `options` block to apply to all tasks, or individually within a specific task to override the global defaults.
159
+
160
+ * `git_clone_args`: A list of extra arguments to pass to `git clone`.
161
+ * `git_merge_args`: A list of extra arguments to pass to `git merge`.
162
+ * `git_update_strategy`: Specifies how updates should be applied. Valid options are `"merge"` (default), `"rebase"`, or `"reset"`.
163
+ * `git_pull`: A boolean indicating whether to run Git pull/fetch operations. Set to `false` to disable updating entirely (default: `true`).
164
+
165
+ Here is an example demonstrating both global and local configurations:
166
+
167
+ ```yaml
168
+ ---
169
+
170
+ options:
171
+ # Apply these defaults to all tasks
172
+ git_update_strategy: reset
173
+ git_clone_args:
174
+ - --recurse-submodules
175
+
176
+ tasks:
177
+ - git: https://github.com/jamescherti/compile-angel.el
178
+ git_pull: false # Do not attempt to update this repo
179
+
180
+ - git: https://github.com/jamescherti/outline-indent.el
181
+ # Override global strategy specifically for this task
182
+ git_update_strategy: merge
183
+ git_merge_args:
184
+ - --no-ff
185
+
186
+ ```
187
+
160
188
  ### How to execute a command before and after a task?
161
189
 
162
190
  To execute a command both before and after a specific task, you can define the `exec_before` and `exec_after` directives within the task configuration. These directives specify commands to be executed at the respective stages of the task lifecycle.
163
191
 
164
192
  Here is an example:
165
- ``` yaml
193
+
194
+ ```yaml
166
195
  ---
167
196
  tasks:
168
197
  - git: https://github.com/jamescherti/easysession.el
@@ -178,6 +207,7 @@ To configure `batchfetch` to handle a specific path, you can define your tasks i
178
207
  #### Example `batchfetch.yml` file:
179
208
 
180
209
  In the following example, the `easysession` task clones two Git repositories:
210
+
181
211
  ```yaml
182
212
  ---
183
213
  tasks:
@@ -213,7 +243,7 @@ batchfetch
213
243
 
214
244
  ## License
215
245
 
216
- Copyright (C) 2024-2025 [James Cherti](https://www.jamescherti.com)
246
+ Copyright (C) 2024-2026 [James Cherti](https://www.jamescherti.com)
217
247
 
218
248
  This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
219
249
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
  #
3
- # Copyright (C) 2024-2025 James Cherti
3
+ # Copyright (C) 2024-2026 James Cherti
4
4
  # URL: https://github.com/jamescherti/batchfetch
5
5
  #
6
6
  # This program is free software: you can redistribute it and/or modify it under
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
  #
3
- # Copyright (C) 2024-2025 James Cherti
3
+ # Copyright (C) 2024-2026 James Cherti
4
4
  # URL: https://github.com/jamescherti/batchfetch
5
5
  #
6
6
  # This program is free software: you can redistribute it and/or modify it under
@@ -16,12 +16,14 @@
16
16
  # You should have received a copy of the GNU General Public License along with
17
17
  # this program. If not, see <https://www.gnu.org/licenses/>.
18
18
  #
19
- "Base class used to run tasks in parallel."
19
+ """Base class used to run tasks in parallel."""
20
+
21
+ from __future__ import annotations
20
22
 
21
23
  import os
22
24
  from copy import deepcopy
23
25
  from pathlib import Path
24
- from typing import Any, Dict, List
26
+ from typing import Any
25
27
 
26
28
  from schema import Optional, Or, Schema
27
29
 
@@ -37,21 +39,21 @@ class DataAlreadyInitialized(Exception):
37
39
 
38
40
 
39
41
  class TaskBase:
40
- def __init__(self, data: Dict[str, Any], options: Dict[str, Any]):
41
- self.global_options_schema: Dict[Any, Any] = {}
42
- self.global_options_values: Dict[str, Any] = options
42
+ def __init__(self, data: dict[str, Any], options: dict[str, Any]) -> None:
43
+ self.global_options_schema: dict[Any, Any] = {}
44
+ self.global_options_values: dict[str, Any] = options
43
45
 
44
- self.task_schema: Dict[Any, Any] = {}
45
- self.task_default_values: Dict[str, Any] = {}
46
+ self.task_schema: dict[Any, Any] = {}
47
+ self.task_default_values: dict[str, Any] = {}
46
48
 
47
49
  self._item_values = data
48
50
 
49
51
  # Variables
50
- self.values: Dict[str, Any] = {}
51
- self.options: Dict[str, Any] = {}
52
+ self.values: dict[str, Any] = {}
53
+ self.options: dict[str, Any] = {}
52
54
  self._values_initialized = False
53
55
 
54
- def _initialize_data(self):
56
+ def _initialize_data(self) -> None:
55
57
  if self._values_initialized:
56
58
  raise DataAlreadyInitialized
57
59
 
@@ -76,10 +78,10 @@ class TaskBase:
76
78
  "changed": False,
77
79
  }
78
80
 
79
- def validate_schema(self):
81
+ def validate_schema(self) -> None:
80
82
  self._initialize_data()
81
83
 
82
- def __getitem__(self, key):
84
+ def __getitem__(self, key: str) -> Any:
83
85
  self._initialize_data()
84
86
 
85
87
  if key in self.values:
@@ -94,8 +96,8 @@ class TaskBase:
94
96
  class TaskBatchFetch(TaskBase):
95
97
  """Plugin downloader base class."""
96
98
 
97
- def __init__(self, data: Dict[str, Any], options: Dict[str, Any]):
98
- new_options: Dict[str, Any] = {"exec_before": [],
99
+ def __init__(self, data: dict[str, Any], options: dict[str, Any]) -> None:
100
+ new_options: dict[str, Any] = {"exec_before": [],
99
101
  "exec_after": [],
100
102
  "ignore_untracked": []}
101
103
  new_options.update(options)
@@ -107,13 +109,13 @@ class TaskBatchFetch(TaskBase):
107
109
  self.env = os.environ.copy()
108
110
 
109
111
  # Default
110
- self.global_options_schema: Dict[Any, Any] = {
112
+ self.global_options_schema: dict[Any, Any] = {
111
113
  Optional("exec_before"): Or([str], str),
112
114
  Optional("exec_after"): Or([str], str),
113
115
  Optional("ignore_untracked"): Or([str], str),
114
116
  }
115
117
 
116
- self.task_schema: Dict[Any, Any] = {
118
+ self.task_schema: dict[Any, Any] = {
117
119
  Optional("path"): str,
118
120
  Optional("delete"): bool,
119
121
 
@@ -124,12 +126,12 @@ class TaskBatchFetch(TaskBase):
124
126
  Optional("ignore_untracked"): Or([str], str),
125
127
  }
126
128
 
127
- self.task_default_values: Dict[str, Any] = {
129
+ self.task_default_values: dict[str, Any] = {
128
130
  # Optional items
129
131
  "delete": False,
130
132
  }
131
133
 
132
- def _initialize_data(self):
134
+ def _initialize_data(self) -> None:
133
135
  try:
134
136
  super()._initialize_data()
135
137
  except DataAlreadyInitialized:
@@ -138,14 +140,14 @@ class TaskBatchFetch(TaskBase):
138
140
  # Mark values as initialized
139
141
  self._values_initialized = True
140
142
 
141
- def _local_task_exec(self, *args, **kwargs):
143
+ def _local_task_exec(self, *args: Any, **kwargs: Any) -> None:
142
144
  stdout = run_indent_str(env=self.env, spaces=self.indent,
143
145
  *args, **kwargs)
144
146
  if not stdout.endswith("\n"):
145
147
  stdout += "\n"
146
148
  self.add_output(stdout)
147
149
 
148
- def _exec_before(self, cwd: os.PathLike = Path(".")):
150
+ def _exec_before(self, cwd: os.PathLike = Path(".")) -> None:
149
151
  self._initialize_data()
150
152
  if self["delete"]:
151
153
  return
@@ -162,9 +164,9 @@ class TaskBatchFetch(TaskBase):
162
164
  if cmd:
163
165
  self._local_task_exec(cmd, cwd=str(cwd))
164
166
 
165
- def _exec_after(self, cwd: os.PathLike = Path(".")):
167
+ def _exec_after(self, cwd: os.PathLike = Path(".")) -> None:
166
168
  self._initialize_data()
167
- if self["delete"] or not self.is_changed():
169
+ if self["delete"] or self.is_error() or not self.is_changed():
168
170
  return
169
171
 
170
172
  # Local
@@ -183,7 +185,7 @@ class TaskBatchFetch(TaskBase):
183
185
  self._initialize_data()
184
186
  return bool(self.values["result"]["changed"])
185
187
 
186
- def set_changed(self, changed: bool):
188
+ def set_changed(self, changed: bool) -> None:
187
189
  self._initialize_data()
188
190
  self.values["result"]["changed"] = changed
189
191
 
@@ -197,15 +199,15 @@ class TaskBatchFetch(TaskBase):
197
199
  self._initialize_data()
198
200
  return bool(self.values["result"]["error"])
199
201
 
200
- def set_error(self, error: bool):
202
+ def set_error(self, error: bool) -> None:
201
203
  self._initialize_data()
202
204
  self.values["result"]["error"] = error
203
205
 
204
- def set_output(self, output: str):
206
+ def set_output(self, output: str) -> None:
205
207
  self._initialize_data()
206
208
  self.values["result"]["output"] = output
207
209
 
208
- def add_output(self, output: str):
210
+ def add_output(self, output: str) -> None:
209
211
  self._initialize_data()
210
212
  self.values["result"]["output"] += output
211
213
 
@@ -214,6 +216,6 @@ class TaskBatchFetch(TaskBase):
214
216
  self._initialize_data()
215
217
  return str(self.values["result"]["output"])
216
218
 
217
- def update(self):
219
+ def update(self) -> dict[str, Any]:
218
220
  self._initialize_data()
219
221
  return self.values