batchfetch 1.2.6__tar.gz → 1.2.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
1
  Metadata-Version: 2.1
2
2
  Name: batchfetch
3
- Version: 1.2.6
3
+ Version: 1.2.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
@@ -22,27 +22,24 @@ License-File: LICENSE
22
22
 
23
23
  ## Introduction
24
24
 
25
- Batchfetch is a command-line tool designed to clone, fetch, and merge multiple Git repositories simultaneously.
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.
26
26
 
27
- 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.
27
+ 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
28
 
29
29
  Batchfetch is ideal for quickly cloning or pulling multiple Git repositories. It is also useful for cloning various addons, such as Vim plugins, Emacs packages, Ansible roles, Ansible collections, and other addons available on websites like GitHub, Codeberg, and GitLab.
30
30
 
31
- ## Features:
32
- - 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.
33
- - Parallel Operations: Utilizes threads to simultaneously Git clone or pull multiple repositories, dramatically reducing wait times.
34
- - User-Friendly Interface: Provides simple and straightforward command-line options that make it easy to get started and effectively manage your repositories.
35
- - Custom Configuration: Allows the use of a YAML configuration file to specify and manage the repositories you interact with, enabling repeatable setups and consistent environments.
36
-
37
31
  ## Installation
38
32
 
33
+ Here is how to install *batchfetch* using [pip](https://pypi.org/project/pip/):
39
34
  ```
40
35
  pip install --user batchfetch
41
36
  ```
42
37
 
43
- The pip command above will install the `batchfetch` executable in the directory `~/.local/bin/`.
38
+ The pip command above installs the *batchfetch* executable in the `~/.local/bin/` directory. Omitting the `--user` flag will install it system-wide.
39
+
40
+ ## Usage
44
41
 
45
- ## Example
42
+ ### Example of a `batchfetch.yaml` file
46
43
 
47
44
  Here is an example of a `batchfetch.yaml` file:
48
45
 
@@ -52,43 +49,55 @@ Here is an example of a `batchfetch.yaml` file:
52
49
  tasks:
53
50
  # Clone the default branch of the general.el repository to the
54
51
  # './general.el' directory
55
- - git: https://github.com/noctuid/general.el
52
+ - git: https://github.com/jamescherti/compile-angel.el
56
53
 
57
54
  # Clone the tag 1.5 of the consult repository to the './consult'
58
55
  # directory
59
- - git: https://github.com/minad/consult
60
- revision: "1.5"
56
+ - git: https://github.com/jamescherti/outline-indent.el
57
+ revision: "1.1.0"
61
58
 
62
59
  # Clone the s.el repository to the './another-name.el' directory
63
- - git: https://github.com/magnars/s.el
64
- path: another-name.el
65
- revision: dda84d38fffdaf0c9b12837b504b402af910d01d
60
+ - git: https://github.com/jamescherti/easysession.el
61
+ path: easysession
62
+ revision: b9c6d9b6134b4981760893254f804a371ffbc899
66
63
 
67
- # Delete './impatient-mode'
68
- - git: https://github.com/skeeto/impatient-mode
64
+ # Delete the local copy of the following repository
65
+ - git: https://github.com/jamescherti/dir-config.el
69
66
  delete: true
70
67
  ```
71
68
 
72
69
  Execute the `batchfetch` command from the same directory as `batchfetch.yml` to make it clone or update the local copies of the repositories above.
73
70
 
74
- ## Usage
71
+ ## Command-line options
75
72
 
76
73
  Here are the various options that `batchfetch` provides, along with descriptions of their usage:
77
74
 
78
75
  ```
79
- usage: batchfetch [--option] [args]
76
+ usage: batchfetch [--option]
80
77
 
81
- Command line interface.
82
-
83
- positional arguments:
84
- N Specify the batchfetch YAML file(s) (default: './batchfetch.yaml').
78
+ Efficiently clone/pull multiple Git repositories in parallel.
85
79
 
86
80
  options:
87
81
  -h, --help show this help message and exit
88
- -j JOBS, --jobs JOBS Run up to N Number of parallel processes (Default: 5).
82
+ -f FILE, --file FILE Specify the batchfetch YAML file (default:
83
+ './batchfetch.yaml').
84
+ -C DIRECTORY, --directory DIRECTORY
85
+ Change the working directory before reading the
86
+ batchfetch.yaml file. If not specified, the directory is
87
+ set to the parent directory of the batchfetch.yaml file.
88
+ -j JOBS, --jobs JOBS Run up to N parallel processes (default: 5).
89
+ Alternatively, the BATCHFETCH_JOBS environment variable
90
+ can be used to configure the number of jobs.
89
91
  -v, --verbose Enable verbose mode.
90
92
  ```
91
93
 
94
+ ## Features
95
+ - 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.
96
+ - Parallel Operations: Utilizes threads to simultaneously Git clone or pull multiple repositories, dramatically reducing wait times.
97
+ - User-Friendly Interface: Provides simple and straightforward command-line options that make it easy to get started and effectively manage your repositories.
98
+ - Custom Configuration: Allows the use of a YAML configuration file to specify and manage the repositories you interact with, enabling repeatable setups and consistent environments.
99
+ - Detect files that should not be present in directories managed by batchfetch, known as untracked files.
100
+
92
101
  ## Frequently Asked Questions
93
102
 
94
103
  ### What are untracked files?
@@ -114,6 +123,43 @@ tasks:
114
123
 
115
124
  By default, *batchfetch.yaml* is the only untracked file that is ignored. The user does not need to add it to the *ignore_untracked_paths* option.
116
125
 
126
+ ### How is the Git local paths handled?
127
+
128
+ When "path:" is specified, that's the path that is used.
129
+
130
+ When "path:" is not specified, Batchfetch attempts to determine the path name by extracting the repository name from the URI (e.g., `https://domain.com/repo` becomes `repo`). If the URL ends with a `.git` extension, it removes the extension (e.g., `https://domain.com/repo.git` becomes `repo`).
131
+
132
+ ### How does Batchfetch detect when a git fetch is necessary?
133
+
134
+ Batchfetch is fast, not only because it runs Git commands in parallel, but also because it intelligently detects whether a `git fetch` is needed, further speeding up the process of downloading data from repositories.
135
+
136
+ 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.
137
+
138
+ 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:
139
+ ``` yaml
140
+ tasks:
141
+ - git: https://github.com/jamescherti/outline-indent.el
142
+ revision: "1.1.0"
143
+
144
+ - git: https://github.com/jamescherti/easysession.el
145
+ path: easysession
146
+ revision: b9c6d9b6134b4981760893254f804a371ffbc899
147
+ ```
148
+
149
+ ### How to execute a command before and after a task?
150
+
151
+ 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.
152
+
153
+ Here is an example:
154
+ ``` yaml
155
+ ---
156
+ tasks:
157
+ - git: https://github.com/jamescherti/easysession.el
158
+ path: easysession
159
+ exec_before: ["sh", "-c", "echo exec_before_task"]
160
+ exec_after: ["sh", "-c", "echo exec_after_task"]
161
+ ```
162
+
117
163
  ## License
118
164
 
119
165
  Copyright (C) 2024 [James Cherti](https://www.jamescherti.com)
@@ -0,0 +1,156 @@
1
+ # Batchfetch - Efficiently clone or pull multiple Git repositories in parallel
2
+
3
+ ## Introduction
4
+
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, freeing you up to focus on what truly matters: your workflow.
6
+
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.
8
+
9
+ Batchfetch is ideal for quickly cloning or pulling multiple Git repositories. It is also useful for cloning various addons, such as Vim plugins, Emacs packages, Ansible roles, Ansible collections, and other addons available on websites like GitHub, Codeberg, and GitLab.
10
+
11
+ ## Installation
12
+
13
+ Here is how to install *batchfetch* using [pip](https://pypi.org/project/pip/):
14
+ ```
15
+ pip install --user batchfetch
16
+ ```
17
+
18
+ The pip command above installs the *batchfetch* executable in the `~/.local/bin/` directory. Omitting the `--user` flag will install it system-wide.
19
+
20
+ ## Usage
21
+
22
+ ### Example of a `batchfetch.yaml` file
23
+
24
+ Here is an example of a `batchfetch.yaml` file:
25
+
26
+ ```yaml
27
+ ---
28
+
29
+ tasks:
30
+ # Clone the default branch of the general.el repository to the
31
+ # './general.el' directory
32
+ - git: https://github.com/jamescherti/compile-angel.el
33
+
34
+ # Clone the tag 1.5 of the consult repository to the './consult'
35
+ # directory
36
+ - git: https://github.com/jamescherti/outline-indent.el
37
+ revision: "1.1.0"
38
+
39
+ # Clone the s.el repository to the './another-name.el' directory
40
+ - git: https://github.com/jamescherti/easysession.el
41
+ path: easysession
42
+ revision: b9c6d9b6134b4981760893254f804a371ffbc899
43
+
44
+ # Delete the local copy of the following repository
45
+ - git: https://github.com/jamescherti/dir-config.el
46
+ delete: true
47
+ ```
48
+
49
+ Execute the `batchfetch` command from the same directory as `batchfetch.yml` to make it clone or update the local copies of the repositories above.
50
+
51
+ ## Command-line options
52
+
53
+ Here are the various options that `batchfetch` provides, along with descriptions of their usage:
54
+
55
+ ```
56
+ usage: batchfetch [--option]
57
+
58
+ Efficiently clone/pull multiple Git repositories in parallel.
59
+
60
+ options:
61
+ -h, --help show this help message and exit
62
+ -f FILE, --file FILE Specify the batchfetch YAML file (default:
63
+ './batchfetch.yaml').
64
+ -C DIRECTORY, --directory DIRECTORY
65
+ Change the working directory before reading the
66
+ batchfetch.yaml file. If not specified, the directory is
67
+ set to the parent directory of the batchfetch.yaml file.
68
+ -j JOBS, --jobs JOBS Run up to N parallel processes (default: 5).
69
+ Alternatively, the BATCHFETCH_JOBS environment variable
70
+ can be used to configure the number of jobs.
71
+ -v, --verbose Enable verbose mode.
72
+ ```
73
+
74
+ ## Features
75
+ - 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.
76
+ - Parallel Operations: Utilizes threads to simultaneously Git clone or pull multiple repositories, dramatically reducing wait times.
77
+ - User-Friendly Interface: Provides simple and straightforward command-line options that make it easy to get started and effectively manage your repositories.
78
+ - Custom Configuration: Allows the use of a YAML configuration file to specify and manage the repositories you interact with, enabling repeatable setups and consistent environments.
79
+ - Detect files that should not be present in directories managed by batchfetch, known as untracked files.
80
+
81
+ ## Frequently Asked Questions
82
+
83
+ ### What are untracked files?
84
+
85
+ The parent directory of the "path:" value defines the managed directory, where the directory of each path is considered as the managed directory.
86
+
87
+ For example, if the "path:" value is `file/my-project`, the managed directory will be `file/`. Any file within `file/` that is not managed by batchfetch will be considered an untracked file.
88
+
89
+ When *batchfetch* encounters an untracked file, it displays an error message to inform users about paths that are not managed by the system. The message provides clear instructions on how to handle these paths by adding them to the `options.ignore_untracked_paths` list, enabling users to manage untracked files effectively.
90
+
91
+ Here is an example of a *batchfetch.yaml* file that enables *batchfetch* to accept a list of untracked files:
92
+
93
+ ``` yaml
94
+ options:
95
+ ignore_untracked_paths:
96
+ - ./test
97
+ - /absolute/path
98
+ - ../relative/path
99
+
100
+ tasks:
101
+ - git: https://github.com/user/project
102
+ ```
103
+
104
+ By default, *batchfetch.yaml* is the only untracked file that is ignored. The user does not need to add it to the *ignore_untracked_paths* option.
105
+
106
+ ### How is the Git local paths handled?
107
+
108
+ When "path:" is specified, that's the path that is used.
109
+
110
+ When "path:" is not specified, Batchfetch attempts to determine the path name by extracting the repository name from the URI (e.g., `https://domain.com/repo` becomes `repo`). If the URL ends with a `.git` extension, it removes the extension (e.g., `https://domain.com/repo.git` becomes `repo`).
111
+
112
+ ### How does Batchfetch detect when a git fetch is necessary?
113
+
114
+ Batchfetch is fast, not only because it runs Git commands in parallel, but also because it intelligently detects whether a `git fetch` is needed, further speeding up the process of downloading data from repositories.
115
+
116
+ 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.
117
+
118
+ 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:
119
+ ``` yaml
120
+ tasks:
121
+ - git: https://github.com/jamescherti/outline-indent.el
122
+ revision: "1.1.0"
123
+
124
+ - git: https://github.com/jamescherti/easysession.el
125
+ path: easysession
126
+ revision: b9c6d9b6134b4981760893254f804a371ffbc899
127
+ ```
128
+
129
+ ### How to execute a command before and after a task?
130
+
131
+ 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.
132
+
133
+ Here is an example:
134
+ ``` yaml
135
+ ---
136
+ tasks:
137
+ - git: https://github.com/jamescherti/easysession.el
138
+ path: easysession
139
+ exec_before: ["sh", "-c", "echo exec_before_task"]
140
+ exec_after: ["sh", "-c", "echo exec_after_task"]
141
+ ```
142
+
143
+ ## License
144
+
145
+ Copyright (C) 2024 [James Cherti](https://www.jamescherti.com)
146
+
147
+ 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.
148
+
149
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
150
+
151
+ You should have received a copy of the GNU General Public License along with this program.
152
+
153
+ ## Links
154
+
155
+ - [batchfetch @GitHub](https://github.com/jamescherti/batchfetch)
156
+ - [batchfetch @Pypi](https://pypi.org/project/batchfetch/)
@@ -25,7 +25,7 @@ import subprocess
25
25
  import sys
26
26
  from concurrent.futures import ThreadPoolExecutor, as_completed
27
27
  from pathlib import Path
28
- from typing import Any, Dict, Set
28
+ from typing import Any, Dict, Set, Union
29
29
 
30
30
  import colorama
31
31
  import yaml # type: ignore
@@ -40,9 +40,10 @@ from .batchfetch_git import BatchFetchGit
40
40
  class BatchFetchCli:
41
41
  """Command-line-interface that downloads."""
42
42
 
43
- def __init__(self, max_workers: int, verbose: bool = False):
43
+ def __init__(self, max_workers: int, verbose: bool, check_untracked: bool):
44
44
  self.cfg: dict = {}
45
45
  self.folder = Path(".")
46
+ self.check_untracked = check_untracked
46
47
  self.tracked_paths: Dict[Path, Set[str]] = {}
47
48
  self.ignore_untracked_paths: Set[Path] = set()
48
49
  self.verbose = verbose
@@ -101,7 +102,8 @@ class BatchFetchCli:
101
102
  self.ignore_untracked_paths.add(Path(path).absolute())
102
103
  self.ignore_untracked_paths.add(Path(path).resolve())
103
104
  untracked_paths = None
104
- if "options" in yaml_dict:
105
+ if "options" in yaml_dict and \
106
+ "ignore_untracked_paths" in yaml_dict["options"]:
105
107
  untracked_paths = \
106
108
  yaml_dict["options"]["ignore_untracked_paths"]
107
109
 
@@ -155,10 +157,12 @@ class BatchFetchCli:
155
157
  print(f"Schema error: {err}.", file=sys.stderr)
156
158
  sys.exit(1)
157
159
 
158
- dest_path = Path(batchfetch_instance["path"]).resolve()
159
- if str(dest_path) in dict_local_dir:
160
+ dest_path = Path(batchfetch_instance["path"]).absolute()
161
+ dest_path2 = Path(batchfetch_instance["path"]).resolve()
162
+ if str(dest_path) in dict_local_dir \
163
+ or str(dest_path2) in dict_local_dir:
160
164
  err_str = ("More than one task have the " +
161
- f"destination path '{dest_path}' (" +
165
+ f"destination path (" +
162
166
  str(task[keyword]) + " and " +
163
167
  str(dict_local_dir[(str(dest_path))]) +
164
168
  ")")
@@ -232,7 +236,8 @@ class BatchFetchCli:
232
236
 
233
237
  return False
234
238
  else:
235
- self._find_untracked_paths()
239
+ if self.check_untracked:
240
+ self._find_untracked_paths()
236
241
 
237
242
  if num_success == 0:
238
243
  print("Nothing to do.")
@@ -269,7 +274,24 @@ class BatchFetchCli:
269
274
 
270
275
  def parse_args():
271
276
  """Parse the command line arguments."""
272
- desc = __doc__
277
+ # Jobs
278
+ try:
279
+ jobs = os.environ["BATCHFETCH_JOBS"]
280
+ except KeyError:
281
+ jobs = 5
282
+ else:
283
+ if jobs:
284
+ jobs = int(jobs)
285
+
286
+ # Check untracked
287
+ try:
288
+ check_untracked = os.environ["BATCHFETCH_CHECK_UNTRACKED"]
289
+ except KeyError:
290
+ check_untracked = False
291
+ else:
292
+ check_untracked = bool(check_untracked)
293
+
294
+ desc = "Efficiently clone/pull multiple Git repositories in parallel."
273
295
  usage = "%(prog)s [--option]"
274
296
  parser = argparse.ArgumentParser(description=desc, usage=usage)
275
297
 
@@ -281,15 +303,40 @@ def parse_args():
281
303
  "(default: './batchfetch.yaml')."))
282
304
 
283
305
  parser.add_argument(
284
- "-j", "--jobs", default="5", required=False,
285
- help="Run up to N Number of parallel processes (Default: 5).",
306
+ "-C",
307
+ "--directory",
308
+ default=None,
309
+ required=False,
310
+ help=("Change the working directory before reading the "
311
+ "batchfetch.yaml file. If not specified, the directory is "
312
+ "set to the parent directory of the batchfetch.yaml file.")
286
313
  )
287
314
 
315
+ parser.add_argument(
316
+ "-j",
317
+ "--jobs",
318
+ default=jobs,
319
+ type=int,
320
+ required=False,
321
+ help=("Run up to N parallel processes (default: 5). "
322
+ "Alternatively, the BATCHFETCH_JOBS environment variable can be "
323
+ "used to configure the number of jobs."))
324
+
288
325
  parser.add_argument(
289
326
  "-v", "--verbose", action="store_true", default=False,
290
327
  help="Enable verbose mode.",
291
328
  )
292
329
 
330
+ parser.add_argument(
331
+ "-u",
332
+ "--check-untracked",
333
+ default=check_untracked,
334
+ action="store_true",
335
+ required=False,
336
+ help=("Abort if untracked files or directories exist. "
337
+ "Alternatively, set the BATCHFETCH_CHECK_UNTRACKED=1 "
338
+ "environment variable to enable this check."))
339
+
293
340
  args = parser.parse_args()
294
341
 
295
342
  if not args.file:
@@ -303,11 +350,16 @@ def parse_args():
303
350
  return args
304
351
 
305
352
 
306
- def run_batchfetch_procedure(file: Path, args) -> int:
353
+ def run_batchfetch_procedure(file: Path,
354
+ directory: Union[None, Path],
355
+ verbose: bool,
356
+ jobs: int,
357
+ check_untracked: bool) -> int:
307
358
  errno = 0
308
- batchfetch_cli = BatchFetchCli(verbose=args.verbose,
309
- max_workers=int(args.jobs))
310
- os.chdir(file.parent)
359
+ batchfetch_cli = BatchFetchCli(verbose=verbose,
360
+ max_workers=int(jobs),
361
+ check_untracked=check_untracked)
362
+ os.chdir(directory)
311
363
  batchfetch_cli.load(file)
312
364
 
313
365
  try:
@@ -337,14 +389,27 @@ def command_line_interface():
337
389
  args = parse_args()
338
390
  done = []
339
391
  file = Path(args.file)
340
- file_resolved = file.resolve()
392
+ file_resolved = file.absolute()
341
393
  if not file_resolved:
342
394
  print(f"Error: cannot resolve the path {file}",
343
395
  file=sys.stderr)
344
396
  sys.exit(1)
345
397
 
346
398
  done.append(file_resolved)
347
- errno |= run_batchfetch_procedure(file, args)
399
+
400
+ args.directory = args.directory if args.directory else file.parent
401
+ if args.verbose and args.jobs:
402
+ print(f"[FILE] {file}")
403
+ print(f"[DIR] {args.directory}")
404
+ print(f"[JOBS] {args.jobs}")
405
+ print(f"[CHECK UNTRACKED] {args.check_untracked}")
406
+ print()
407
+
408
+ errno |= run_batchfetch_procedure(file=file,
409
+ directory=args.directory,
410
+ verbose=args.verbose,
411
+ jobs=args.jobs,
412
+ check_untracked=args.check_untracked)
348
413
 
349
414
  sys.exit(errno)
350
415
  except BrokenPipeError:
@@ -93,9 +93,12 @@ class BatchFetchGit(TaskBatchFetch):
93
93
  self.values[self.main_key].rstrip("/")
94
94
 
95
95
  if "path" not in self.values:
96
- self.values["path"] = \
97
- posixpath.basename(
98
- self.values[self.main_key]) # type: ignore
96
+ path = \
97
+ posixpath.basename(self.values[self.main_key]) # type: ignore
98
+ # Remove .git from the file name
99
+ if path.endswith(".git"):
100
+ path = path[:-4]
101
+ self.values["path"] = path
99
102
 
100
103
  def update(self):
101
104
  """Clone or update a Git repository."""
@@ -120,7 +123,7 @@ class BatchFetchGit(TaskBatchFetch):
120
123
 
121
124
  self.add_output(f"[GIT {update_type}] {self[self.main_key]}"
122
125
  + (f" (Ref: {self['revision']})"
123
- if self["revision"] else "") + "\n")
126
+ if self["revision"] else "") + "\n")
124
127
 
125
128
  try:
126
129
  # Delete
@@ -203,8 +206,8 @@ class BatchFetchGit(TaskBatchFetch):
203
206
  # directly to a commit. You need to resolve it to the
204
207
  # commit it points to. Using `git rev-parse
205
208
  # <tagname>^{commit}` allows getting the right revision.
206
- commit_ref = self._git_tags(self["revision"]
207
- + "^{commit}")[0]
209
+ commit_ref = self._git_rev_parse_verify(self["revision"]
210
+ + "^{commit}")[0]
208
211
  self.branch_commit_ref = commit_ref.strip()
209
212
  except GitRevisionDoesNotExist:
210
213
  pass
@@ -242,9 +245,7 @@ class BatchFetchGit(TaskBatchFetch):
242
245
  def _repo_fetch(self):
243
246
  # Merge
244
247
  do_git_fetch = self["git_pull"]
245
- if self.is_branch:
246
- do_git_fetch = True
247
- elif not self["revision"]:
248
+ if not self["revision"]:
248
249
  do_git_fetch = True
249
250
  self.add_output(self.indent_spaces
250
251
  + "[INFO] Git fetch origin reason: "
@@ -256,12 +257,9 @@ class BatchFetchGit(TaskBatchFetch):
256
257
  try:
257
258
  # Check if the revision such as
258
259
  # 0560fe21d1173b2221fd8c600fab818f7eecbad4 exist
259
- commit_ref = self._git_tags(self["revision"])[0]
260
+ commit_ref = self._git_rev_parse_verify(self["revision"])[0]
260
261
  commit_ref = commit_ref.strip()
261
262
  except GitRevisionDoesNotExist:
262
- pass
263
-
264
- if not commit_ref and not self.is_branch:
265
263
  do_git_fetch = True
266
264
  self.add_output(
267
265
  self.indent_spaces
@@ -269,6 +267,7 @@ class BatchFetchGit(TaskBatchFetch):
269
267
  + f"The revision does not exist: {self['revision']}"
270
268
  + "\n")
271
269
 
270
+ # The revision exists, but if it a branch, git pull anyway
272
271
  if not do_git_fetch:
273
272
  try:
274
273
  # Check if the branch is a tag or a branch
@@ -284,31 +283,6 @@ class BatchFetchGit(TaskBatchFetch):
284
283
  except subprocess.CalledProcessError:
285
284
  pass
286
285
 
287
- if not do_git_fetch and commit_ref:
288
- # This is to check if a tag has been changed
289
- try:
290
- # If the tag is annotated, it points to a tag object,
291
- # not directly to a commit. You need to resolve it to
292
- # the commit it points to. Using `git rev-parse
293
- # <tagname>^{commit}` allows getting the right
294
- # revision.
295
- commit_ref_head = self._git_tags("HEAD^{commit}")[0]
296
- commit_ref_head = commit_ref_head.strip()
297
- except GitRevisionDoesNotExist:
298
- # HEAD is detached
299
- commit_ref_head = None
300
-
301
- # The wanted commit revision does not exist
302
- # Or the commit ref of HEAD hasn't changed
303
- if not commit_ref_head or commit_ref_head != commit_ref:
304
- self.add_output(
305
- self.indent_spaces
306
- + "[INFO] Git fetch origin reason: "
307
- f"Commit ref head '{commit_ref_head}' != "
308
- f"commit ref '{commit_ref_head}'"
309
- "\n")
310
- do_git_fetch = True
311
-
312
286
  if not do_git_fetch:
313
287
  self.add_output(self.indent_spaces + "[INFO] git fetch ignored\n")
314
288
  return False
@@ -379,6 +353,7 @@ class BatchFetchGit(TaskBatchFetch):
379
353
  return origin_url
380
354
 
381
355
  def _git_is_local_branch(self, branch: str) -> bool:
356
+ "Return True if it is a local branch that exists."
382
357
  try:
383
358
  stdout, _ = run_simple(["git", "rev-parse", "--symbolic-full-name",
384
359
  branch], env=self.env,
@@ -395,11 +370,11 @@ class BatchFetchGit(TaskBatchFetch):
395
370
 
396
371
  return False
397
372
 
398
- def _git_tags(self, branch: str) -> List[str]:
373
+ def _git_rev_parse_verify(self, revision: str) -> List[str]:
399
374
  stdout: List[str] = []
400
375
  error = False
401
376
  try:
402
- stdout, _ = run_simple(["git", "rev-parse", "--verify", branch],
377
+ stdout, _ = run_simple(["git", "rev-parse", "--verify", revision],
403
378
  env=self.env,
404
379
  cwd=self.git_local_dir)
405
380
  if not stdout:
@@ -409,7 +384,7 @@ class BatchFetchGit(TaskBatchFetch):
409
384
 
410
385
  if error:
411
386
  raise GitRevisionDoesNotExist(
412
- f"The revision '{branch}' does not exist.")
387
+ f"The revision '{revision}' does not exist.")
413
388
 
414
389
  return stdout
415
390
 
@@ -429,13 +404,14 @@ class BatchFetchGit(TaskBatchFetch):
429
404
  # branch is a commit revision instead of a tag
430
405
  try:
431
406
  # Check if the branch exists
432
- git_ref_branch = self._git_tags("origin/"
433
- + self["revision"]
434
- + "^{commit}")[0]
407
+ git_ref_branch = self._git_rev_parse_verify("origin/"
408
+ + self["revision"]
409
+ + "^{commit}")[0]
435
410
  except GitRevisionDoesNotExist:
436
411
  # Check if the commit ref exists
437
412
  try:
438
- git_ref_branch = self._git_tags(self["revision"])[0]
413
+ git_ref_branch = self._git_rev_parse_verify(self["revision"])[
414
+ 0]
439
415
  except GitRevisionDoesNotExist as err:
440
416
  raise BatchFetchError(f"The branch '{self['revision']}' "
441
417
  "does not exist.") from err
@@ -112,7 +112,7 @@ def run_indent(cmd: Union[List[str], str], spaces: int = 4,
112
112
  cmd = shlex.split(cmd) if isinstance(cmd, str) else cmd
113
113
  stdout, stderr = run_simple(cmd=cmd, **kwargs)
114
114
  stdout = indent_raw_output([f"[RUN] {list2cmdline(cmd)}"], spaces) + \
115
- indent_raw_output(stdout, spaces)
115
+ indent_raw_output(stdout, spaces + spaces)
116
116
  stderr = indent_raw_output(stderr, spaces)
117
117
 
118
118
  return (stdout, stderr)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: batchfetch
3
- Version: 1.2.6
3
+ Version: 1.2.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
@@ -22,27 +22,24 @@ License-File: LICENSE
22
22
 
23
23
  ## Introduction
24
24
 
25
- Batchfetch is a command-line tool designed to clone, fetch, and merge multiple Git repositories simultaneously.
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.
26
26
 
27
- 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.
27
+ 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
28
 
29
29
  Batchfetch is ideal for quickly cloning or pulling multiple Git repositories. It is also useful for cloning various addons, such as Vim plugins, Emacs packages, Ansible roles, Ansible collections, and other addons available on websites like GitHub, Codeberg, and GitLab.
30
30
 
31
- ## Features:
32
- - 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.
33
- - Parallel Operations: Utilizes threads to simultaneously Git clone or pull multiple repositories, dramatically reducing wait times.
34
- - User-Friendly Interface: Provides simple and straightforward command-line options that make it easy to get started and effectively manage your repositories.
35
- - Custom Configuration: Allows the use of a YAML configuration file to specify and manage the repositories you interact with, enabling repeatable setups and consistent environments.
36
-
37
31
  ## Installation
38
32
 
33
+ Here is how to install *batchfetch* using [pip](https://pypi.org/project/pip/):
39
34
  ```
40
35
  pip install --user batchfetch
41
36
  ```
42
37
 
43
- The pip command above will install the `batchfetch` executable in the directory `~/.local/bin/`.
38
+ The pip command above installs the *batchfetch* executable in the `~/.local/bin/` directory. Omitting the `--user` flag will install it system-wide.
39
+
40
+ ## Usage
44
41
 
45
- ## Example
42
+ ### Example of a `batchfetch.yaml` file
46
43
 
47
44
  Here is an example of a `batchfetch.yaml` file:
48
45
 
@@ -52,43 +49,55 @@ Here is an example of a `batchfetch.yaml` file:
52
49
  tasks:
53
50
  # Clone the default branch of the general.el repository to the
54
51
  # './general.el' directory
55
- - git: https://github.com/noctuid/general.el
52
+ - git: https://github.com/jamescherti/compile-angel.el
56
53
 
57
54
  # Clone the tag 1.5 of the consult repository to the './consult'
58
55
  # directory
59
- - git: https://github.com/minad/consult
60
- revision: "1.5"
56
+ - git: https://github.com/jamescherti/outline-indent.el
57
+ revision: "1.1.0"
61
58
 
62
59
  # Clone the s.el repository to the './another-name.el' directory
63
- - git: https://github.com/magnars/s.el
64
- path: another-name.el
65
- revision: dda84d38fffdaf0c9b12837b504b402af910d01d
60
+ - git: https://github.com/jamescherti/easysession.el
61
+ path: easysession
62
+ revision: b9c6d9b6134b4981760893254f804a371ffbc899
66
63
 
67
- # Delete './impatient-mode'
68
- - git: https://github.com/skeeto/impatient-mode
64
+ # Delete the local copy of the following repository
65
+ - git: https://github.com/jamescherti/dir-config.el
69
66
  delete: true
70
67
  ```
71
68
 
72
69
  Execute the `batchfetch` command from the same directory as `batchfetch.yml` to make it clone or update the local copies of the repositories above.
73
70
 
74
- ## Usage
71
+ ## Command-line options
75
72
 
76
73
  Here are the various options that `batchfetch` provides, along with descriptions of their usage:
77
74
 
78
75
  ```
79
- usage: batchfetch [--option] [args]
76
+ usage: batchfetch [--option]
80
77
 
81
- Command line interface.
82
-
83
- positional arguments:
84
- N Specify the batchfetch YAML file(s) (default: './batchfetch.yaml').
78
+ Efficiently clone/pull multiple Git repositories in parallel.
85
79
 
86
80
  options:
87
81
  -h, --help show this help message and exit
88
- -j JOBS, --jobs JOBS Run up to N Number of parallel processes (Default: 5).
82
+ -f FILE, --file FILE Specify the batchfetch YAML file (default:
83
+ './batchfetch.yaml').
84
+ -C DIRECTORY, --directory DIRECTORY
85
+ Change the working directory before reading the
86
+ batchfetch.yaml file. If not specified, the directory is
87
+ set to the parent directory of the batchfetch.yaml file.
88
+ -j JOBS, --jobs JOBS Run up to N parallel processes (default: 5).
89
+ Alternatively, the BATCHFETCH_JOBS environment variable
90
+ can be used to configure the number of jobs.
89
91
  -v, --verbose Enable verbose mode.
90
92
  ```
91
93
 
94
+ ## Features
95
+ - 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.
96
+ - Parallel Operations: Utilizes threads to simultaneously Git clone or pull multiple repositories, dramatically reducing wait times.
97
+ - User-Friendly Interface: Provides simple and straightforward command-line options that make it easy to get started and effectively manage your repositories.
98
+ - Custom Configuration: Allows the use of a YAML configuration file to specify and manage the repositories you interact with, enabling repeatable setups and consistent environments.
99
+ - Detect files that should not be present in directories managed by batchfetch, known as untracked files.
100
+
92
101
  ## Frequently Asked Questions
93
102
 
94
103
  ### What are untracked files?
@@ -114,6 +123,43 @@ tasks:
114
123
 
115
124
  By default, *batchfetch.yaml* is the only untracked file that is ignored. The user does not need to add it to the *ignore_untracked_paths* option.
116
125
 
126
+ ### How is the Git local paths handled?
127
+
128
+ When "path:" is specified, that's the path that is used.
129
+
130
+ When "path:" is not specified, Batchfetch attempts to determine the path name by extracting the repository name from the URI (e.g., `https://domain.com/repo` becomes `repo`). If the URL ends with a `.git` extension, it removes the extension (e.g., `https://domain.com/repo.git` becomes `repo`).
131
+
132
+ ### How does Batchfetch detect when a git fetch is necessary?
133
+
134
+ Batchfetch is fast, not only because it runs Git commands in parallel, but also because it intelligently detects whether a `git fetch` is needed, further speeding up the process of downloading data from repositories.
135
+
136
+ 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.
137
+
138
+ 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:
139
+ ``` yaml
140
+ tasks:
141
+ - git: https://github.com/jamescherti/outline-indent.el
142
+ revision: "1.1.0"
143
+
144
+ - git: https://github.com/jamescherti/easysession.el
145
+ path: easysession
146
+ revision: b9c6d9b6134b4981760893254f804a371ffbc899
147
+ ```
148
+
149
+ ### How to execute a command before and after a task?
150
+
151
+ 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.
152
+
153
+ Here is an example:
154
+ ``` yaml
155
+ ---
156
+ tasks:
157
+ - git: https://github.com/jamescherti/easysession.el
158
+ path: easysession
159
+ exec_before: ["sh", "-c", "echo exec_before_task"]
160
+ exec_after: ["sh", "-c", "echo exec_after_task"]
161
+ ```
162
+
117
163
  ## License
118
164
 
119
165
  Copyright (C) 2024 [James Cherti](https://www.jamescherti.com)
@@ -22,7 +22,7 @@ from setuptools import find_packages, setup
22
22
 
23
23
  setup(
24
24
  name="batchfetch",
25
- version="1.2.6",
25
+ version="1.2.7",
26
26
  packages=find_packages(),
27
27
  description="Efficiently clone and pull multiple Git repositories.",
28
28
  license="GPLv3",
@@ -1,110 +0,0 @@
1
- # Batchfetch - Efficiently clone or pull multiple Git repositories in parallel
2
-
3
- ## Introduction
4
-
5
- Batchfetch is a command-line tool designed to clone, fetch, and merge multiple Git repositories simultaneously.
6
-
7
- 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.
8
-
9
- Batchfetch is ideal for quickly cloning or pulling multiple Git repositories. It is also useful for cloning various addons, such as Vim plugins, Emacs packages, Ansible roles, Ansible collections, and other addons available on websites like GitHub, Codeberg, and GitLab.
10
-
11
- ## Features:
12
- - 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.
13
- - Parallel Operations: Utilizes threads to simultaneously Git clone or pull multiple repositories, dramatically reducing wait times.
14
- - User-Friendly Interface: Provides simple and straightforward command-line options that make it easy to get started and effectively manage your repositories.
15
- - Custom Configuration: Allows the use of a YAML configuration file to specify and manage the repositories you interact with, enabling repeatable setups and consistent environments.
16
-
17
- ## Installation
18
-
19
- ```
20
- pip install --user batchfetch
21
- ```
22
-
23
- The pip command above will install the `batchfetch` executable in the directory `~/.local/bin/`.
24
-
25
- ## Example
26
-
27
- Here is an example of a `batchfetch.yaml` file:
28
-
29
- ```yaml
30
- ---
31
-
32
- tasks:
33
- # Clone the default branch of the general.el repository to the
34
- # './general.el' directory
35
- - git: https://github.com/noctuid/general.el
36
-
37
- # Clone the tag 1.5 of the consult repository to the './consult'
38
- # directory
39
- - git: https://github.com/minad/consult
40
- revision: "1.5"
41
-
42
- # Clone the s.el repository to the './another-name.el' directory
43
- - git: https://github.com/magnars/s.el
44
- path: another-name.el
45
- revision: dda84d38fffdaf0c9b12837b504b402af910d01d
46
-
47
- # Delete './impatient-mode'
48
- - git: https://github.com/skeeto/impatient-mode
49
- delete: true
50
- ```
51
-
52
- Execute the `batchfetch` command from the same directory as `batchfetch.yml` to make it clone or update the local copies of the repositories above.
53
-
54
- ## Usage
55
-
56
- Here are the various options that `batchfetch` provides, along with descriptions of their usage:
57
-
58
- ```
59
- usage: batchfetch [--option] [args]
60
-
61
- Command line interface.
62
-
63
- positional arguments:
64
- N Specify the batchfetch YAML file(s) (default: './batchfetch.yaml').
65
-
66
- options:
67
- -h, --help show this help message and exit
68
- -j JOBS, --jobs JOBS Run up to N Number of parallel processes (Default: 5).
69
- -v, --verbose Enable verbose mode.
70
- ```
71
-
72
- ## Frequently Asked Questions
73
-
74
- ### What are untracked files?
75
-
76
- The parent directory of the "path:" value defines the managed directory, where the directory of each path is considered as the managed directory.
77
-
78
- For example, if the "path:" value is `file/my-project`, the managed directory will be `file/`. Any file within `file/` that is not managed by batchfetch will be considered an untracked file.
79
-
80
- When *batchfetch* encounters an untracked file, it displays an error message to inform users about paths that are not managed by the system. The message provides clear instructions on how to handle these paths by adding them to the `options.ignore_untracked_paths` list, enabling users to manage untracked files effectively.
81
-
82
- Here is an example of a *batchfetch.yaml* file that enables *batchfetch* to accept a list of untracked files:
83
-
84
- ``` yaml
85
- options:
86
- ignore_untracked_paths:
87
- - ./test
88
- - /absolute/path
89
- - ../relative/path
90
-
91
- tasks:
92
- - git: https://github.com/user/project
93
- ```
94
-
95
- By default, *batchfetch.yaml* is the only untracked file that is ignored. The user does not need to add it to the *ignore_untracked_paths* option.
96
-
97
- ## License
98
-
99
- Copyright (C) 2024 [James Cherti](https://www.jamescherti.com)
100
-
101
- 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.
102
-
103
- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
104
-
105
- You should have received a copy of the GNU General Public License along with this program.
106
-
107
- ## Links
108
-
109
- - [batchfetch @GitHub](https://github.com/jamescherti/batchfetch)
110
- - [batchfetch @Pypi](https://pypi.org/project/batchfetch/)
File without changes
File without changes