git-rexec 1.0.0__tar.gz → 1.0.2__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.
- git_rexec-1.0.2/.gitignore +6 -0
- {git_rexec-1.0.0 → git_rexec-1.0.2}/PKG-INFO +18 -9
- {git_rexec-1.0.0 → git_rexec-1.0.2}/README.md +16 -6
- {git_rexec-1.0.0 → git_rexec-1.0.2}/git-rexec +131 -43
- {git_rexec-1.0.0 → git_rexec-1.0.2}/git_rexec.egg-info/PKG-INFO +18 -9
- {git_rexec-1.0.0 → git_rexec-1.0.2}/git_rexec.egg-info/SOURCES.txt +1 -0
- {git_rexec-1.0.0 → git_rexec-1.0.2}/pyproject.toml +2 -3
- {git_rexec-1.0.0 → git_rexec-1.0.2}/git_rexec.egg-info/dependency_links.txt +0 -0
- {git_rexec-1.0.0 → git_rexec-1.0.2}/git_rexec.egg-info/requires.txt +0 -0
- {git_rexec-1.0.0 → git_rexec-1.0.2}/git_rexec.egg-info/top_level.txt +0 -0
- {git_rexec-1.0.0 → git_rexec-1.0.2}/setup.cfg +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-rexec
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Locate Git repositories and execute commands against them in parallel.
|
|
5
5
|
Author: James Cherti
|
|
6
|
-
License: GPL-3.0-or-later
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/jamescherti/git-rexec
|
|
8
8
|
Keywords: git,parallel,executor,automation
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
10
|
Classifier: Operating System :: OS Independent
|
|
12
11
|
Classifier: Environment :: Console
|
|
13
12
|
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
@@ -19,7 +18,7 @@ Requires-Dist: setproctitle; extra == "extras"
|
|
|
19
18
|
|
|
20
19
|
# git-rexec: Find Git Repositories and Execute Commands Against Them, either Sequentially or in Parallel
|
|
21
20
|
|
|
22
|
-
The [git-rexec](https://github.com/jamescherti/git-rexec/) command-line tool
|
|
21
|
+
The [git-rexec](https://github.com/jamescherti/git-rexec/) command-line tool recursively locates Git repositories within a directory and executes commands against them, either sequentially or in parallel.
|
|
23
22
|
|
|
24
23
|
Here are examples demonstrating how to use `git-rexec`:
|
|
25
24
|
- Execute `git status -s` across all discovered Git repositories (found by searching recursively under the current working directory) in parallel (`-p` or `--parallel`):
|
|
@@ -32,6 +31,12 @@ Here are examples demonstrating how to use `git-rexec`:
|
|
|
32
31
|
git-rexec -j 5 --parallel -- git fetch
|
|
33
32
|
```
|
|
34
33
|
|
|
34
|
+
|
|
35
|
+
- Include sub-repositories (e.g., Git worktrees and submodules) alongside standard Git repositories during discovery (`-s` or `--include-sub-repos`), and execute `git status` against them:
|
|
36
|
+
```bash
|
|
37
|
+
git-rexec -s -p -- git status
|
|
38
|
+
```
|
|
39
|
+
|
|
35
40
|
- Target a specific base directory (`~/projects`) using the `-C` flag to recursively discover repositories within it, while explicitly excluding a specific subfolder (`~/projects/archive`). This example executes `git status -s` in parallel for all discovered repositories except those within the excluded path:
|
|
36
41
|
```bash
|
|
37
42
|
git-rexec -C ~/projects --exclude-dir ~/projects/archive --parallel -- git status -s
|
|
@@ -42,15 +47,21 @@ Here are examples demonstrating how to use `git-rexec`:
|
|
|
42
47
|
git-rexec --if-exec 'sh -c "test -f README.md"' --parallel -- wc -l README.md
|
|
43
48
|
```
|
|
44
49
|
|
|
50
|
+
- Print the paths of all discovered Git repositories:
|
|
51
|
+
```bash
|
|
52
|
+
git-rexec --print
|
|
53
|
+
```
|
|
54
|
+
|
|
45
55
|
If this helps your workflow, please support the project by **⭐ starring git-rexec on GitHub** and sharing it on your website, blog, Mastodon, Reddit, X, LinkedIn, or other social media platforms to help more Git users discover its benefits.
|
|
46
56
|
|
|
47
57
|
## Features
|
|
48
58
|
|
|
49
59
|
- Recursively discover Git repositories starting from a specified root directory.
|
|
60
|
+
- Optional flag to include sub-repositories (e.g., Git worktrees and submodules) in the execution target list.
|
|
50
61
|
- Execute shell commands across multiple repositories in parallel using worker threads.
|
|
51
62
|
- Filter target repositories based on the exit code of a conditional check (`--if-exec`).
|
|
52
63
|
- Exclude specific directories from the search path.
|
|
53
|
-
-
|
|
64
|
+
- Export discovered repository paths for integration with other shell tools (using `--print` or `--print0`).
|
|
54
65
|
|
|
55
66
|
## Installation
|
|
56
67
|
|
|
@@ -67,7 +78,7 @@ sudo cp git-rexec /usr/local/bin/
|
|
|
67
78
|
Install the package directly from the Git repository using `pip`:
|
|
68
79
|
|
|
69
80
|
```bash
|
|
70
|
-
pip install --user git
|
|
81
|
+
pip install --user git-rexec
|
|
71
82
|
```
|
|
72
83
|
|
|
73
84
|
## Dependencies
|
|
@@ -75,7 +86,6 @@ pip install --user git+https://github.com/jamescherti/git-rexec
|
|
|
75
86
|
### System Dependencies
|
|
76
87
|
|
|
77
88
|
- `git`: Required for repository validation and execution.
|
|
78
|
-
- `fd` (Optional): Highly recommended for faster repository discovery.
|
|
79
89
|
|
|
80
90
|
### Python Dependencies (Optional)
|
|
81
91
|
|
|
@@ -86,14 +96,12 @@ You can install the optional Python dependencies via pip:
|
|
|
86
96
|
|
|
87
97
|
```bash
|
|
88
98
|
pip install colorama setproctitle
|
|
89
|
-
|
|
90
99
|
```
|
|
91
100
|
|
|
92
101
|
## Usage
|
|
93
102
|
|
|
94
103
|
```bash
|
|
95
104
|
git-rexec [OPTIONS] [exec_cmd ...]
|
|
96
|
-
|
|
97
105
|
```
|
|
98
106
|
|
|
99
107
|
*(Assuming the `git-rexec` script is executable and in your PATH.)*
|
|
@@ -109,6 +117,7 @@ git-rexec [OPTIONS] [exec_cmd ...]
|
|
|
109
117
|
- `-p, --parallel`: Execute the command in parallel using threads.
|
|
110
118
|
- `-i, --if-exec <command>`: Execute the main command only if this check command returns an exit code of `0`.
|
|
111
119
|
- `-j, --jobs <int>`: The maximum number of concurrent workers/processors to use for parallel execution. Defaults to the number of CPU cores available.
|
|
120
|
+
- `-s, --include-sub-repos`: Include sub-repositories (e.g., Git worktrees and submodules) in the search target list.
|
|
112
121
|
- `-h, --help`: Show the help message and exit.
|
|
113
122
|
- `-q, --quiet`: Quiet mode. Suppresses the informational tracking headers (`[EXEC]` and `[EXEC-P]`) that prefix execution output. In sequential mode, it hides the `[EXEC]` repository delimiter line entirely; in parallel mode (`-p`), it strips the yellow `[EXEC-P]` header track and removes the four-space indentation, printing only the raw, unindented stdout and stderr streams. This flag has no effect when no execution command is supplied, allowing discovered repository paths to print normally.
|
|
114
123
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# git-rexec: Find Git Repositories and Execute Commands Against Them, either Sequentially or in Parallel
|
|
2
2
|
|
|
3
|
-
The [git-rexec](https://github.com/jamescherti/git-rexec/) command-line tool
|
|
3
|
+
The [git-rexec](https://github.com/jamescherti/git-rexec/) command-line tool recursively locates Git repositories within a directory and executes commands against them, either sequentially or in parallel.
|
|
4
4
|
|
|
5
5
|
Here are examples demonstrating how to use `git-rexec`:
|
|
6
6
|
- Execute `git status -s` across all discovered Git repositories (found by searching recursively under the current working directory) in parallel (`-p` or `--parallel`):
|
|
@@ -13,6 +13,12 @@ Here are examples demonstrating how to use `git-rexec`:
|
|
|
13
13
|
git-rexec -j 5 --parallel -- git fetch
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
- Include sub-repositories (e.g., Git worktrees and submodules) alongside standard Git repositories during discovery (`-s` or `--include-sub-repos`), and execute `git status` against them:
|
|
18
|
+
```bash
|
|
19
|
+
git-rexec -s -p -- git status
|
|
20
|
+
```
|
|
21
|
+
|
|
16
22
|
- Target a specific base directory (`~/projects`) using the `-C` flag to recursively discover repositories within it, while explicitly excluding a specific subfolder (`~/projects/archive`). This example executes `git status -s` in parallel for all discovered repositories except those within the excluded path:
|
|
17
23
|
```bash
|
|
18
24
|
git-rexec -C ~/projects --exclude-dir ~/projects/archive --parallel -- git status -s
|
|
@@ -23,15 +29,21 @@ Here are examples demonstrating how to use `git-rexec`:
|
|
|
23
29
|
git-rexec --if-exec 'sh -c "test -f README.md"' --parallel -- wc -l README.md
|
|
24
30
|
```
|
|
25
31
|
|
|
32
|
+
- Print the paths of all discovered Git repositories:
|
|
33
|
+
```bash
|
|
34
|
+
git-rexec --print
|
|
35
|
+
```
|
|
36
|
+
|
|
26
37
|
If this helps your workflow, please support the project by **⭐ starring git-rexec on GitHub** and sharing it on your website, blog, Mastodon, Reddit, X, LinkedIn, or other social media platforms to help more Git users discover its benefits.
|
|
27
38
|
|
|
28
39
|
## Features
|
|
29
40
|
|
|
30
41
|
- Recursively discover Git repositories starting from a specified root directory.
|
|
42
|
+
- Optional flag to include sub-repositories (e.g., Git worktrees and submodules) in the execution target list.
|
|
31
43
|
- Execute shell commands across multiple repositories in parallel using worker threads.
|
|
32
44
|
- Filter target repositories based on the exit code of a conditional check (`--if-exec`).
|
|
33
45
|
- Exclude specific directories from the search path.
|
|
34
|
-
-
|
|
46
|
+
- Export discovered repository paths for integration with other shell tools (using `--print` or `--print0`).
|
|
35
47
|
|
|
36
48
|
## Installation
|
|
37
49
|
|
|
@@ -48,7 +60,7 @@ sudo cp git-rexec /usr/local/bin/
|
|
|
48
60
|
Install the package directly from the Git repository using `pip`:
|
|
49
61
|
|
|
50
62
|
```bash
|
|
51
|
-
pip install --user git
|
|
63
|
+
pip install --user git-rexec
|
|
52
64
|
```
|
|
53
65
|
|
|
54
66
|
## Dependencies
|
|
@@ -56,7 +68,6 @@ pip install --user git+https://github.com/jamescherti/git-rexec
|
|
|
56
68
|
### System Dependencies
|
|
57
69
|
|
|
58
70
|
- `git`: Required for repository validation and execution.
|
|
59
|
-
- `fd` (Optional): Highly recommended for faster repository discovery.
|
|
60
71
|
|
|
61
72
|
### Python Dependencies (Optional)
|
|
62
73
|
|
|
@@ -67,14 +78,12 @@ You can install the optional Python dependencies via pip:
|
|
|
67
78
|
|
|
68
79
|
```bash
|
|
69
80
|
pip install colorama setproctitle
|
|
70
|
-
|
|
71
81
|
```
|
|
72
82
|
|
|
73
83
|
## Usage
|
|
74
84
|
|
|
75
85
|
```bash
|
|
76
86
|
git-rexec [OPTIONS] [exec_cmd ...]
|
|
77
|
-
|
|
78
87
|
```
|
|
79
88
|
|
|
80
89
|
*(Assuming the `git-rexec` script is executable and in your PATH.)*
|
|
@@ -90,6 +99,7 @@ git-rexec [OPTIONS] [exec_cmd ...]
|
|
|
90
99
|
- `-p, --parallel`: Execute the command in parallel using threads.
|
|
91
100
|
- `-i, --if-exec <command>`: Execute the main command only if this check command returns an exit code of `0`.
|
|
92
101
|
- `-j, --jobs <int>`: The maximum number of concurrent workers/processors to use for parallel execution. Defaults to the number of CPU cores available.
|
|
102
|
+
- `-s, --include-sub-repos`: Include sub-repositories (e.g., Git worktrees and submodules) in the search target list.
|
|
93
103
|
- `-h, --help`: Show the help message and exit.
|
|
94
104
|
- `-q, --quiet`: Quiet mode. Suppresses the informational tracking headers (`[EXEC]` and `[EXEC-P]`) that prefix execution output. In sequential mode, it hides the `[EXEC]` repository delimiter line entirely; in parallel mode (`-p`), it strips the yellow `[EXEC-P]` header track and removes the four-space indentation, printing only the raw, unindented stdout and stderr streams. This flag has no effect when no execution command is supplied, allowing discovered repository paths to print normally.
|
|
95
105
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#
|
|
7
7
|
# Author: James Cherti
|
|
8
8
|
# URL: https://github.com/jamescherti/git-rexec
|
|
9
|
+
# Version: 1.0.2
|
|
9
10
|
#
|
|
10
11
|
# Copyright (C) 2019-2026 James Cherti
|
|
11
12
|
#
|
|
@@ -100,51 +101,95 @@ def git_toplevel(repo_path: Path) -> Optional[Path]:
|
|
|
100
101
|
return None
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
def
|
|
104
|
-
"""
|
|
104
|
+
def git_py_find_repo(root: Path) -> tuple[set[Path], set[Path]]:
|
|
105
|
+
"""Recursively find all Git repositories under the root path using Python.
|
|
106
|
+
|
|
107
|
+
:param root: Directory to search under.
|
|
108
|
+
:return: A tuple containing standard repository paths and sub-repository
|
|
109
|
+
paths.
|
|
110
|
+
"""
|
|
111
|
+
repos: set[Path] = set()
|
|
112
|
+
sub_repos: set[Path] = set()
|
|
113
|
+
|
|
114
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
115
|
+
if ".git" in dirnames:
|
|
116
|
+
repos.add(Path(dirpath).absolute())
|
|
117
|
+
elif ".git" in filenames:
|
|
118
|
+
sub_repos.add(Path(dirpath).absolute())
|
|
119
|
+
else:
|
|
120
|
+
continue
|
|
105
121
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
# Clear dirnames to prevent os.walk from descending into
|
|
123
|
+
# subdirectories
|
|
124
|
+
dirnames.clear()
|
|
125
|
+
|
|
126
|
+
return repos, sub_repos
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_sub_repos(repo_path: Path) -> set[Path]:
|
|
130
|
+
"""Extract worktrees and submodules from a Git repository.
|
|
131
|
+
|
|
132
|
+
:param repo_path: The path to the standard repository.
|
|
133
|
+
:return: A set of absolute paths to worktrees and submodules.
|
|
109
134
|
"""
|
|
110
|
-
|
|
135
|
+
sub_repos: set[Path] = set()
|
|
136
|
+
|
|
137
|
+
# Discover worktrees
|
|
138
|
+
# pylint: disable=too-many-try-statements
|
|
111
139
|
try:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
[
|
|
115
|
-
'fd', '--type', 'd', '--hidden',
|
|
116
|
-
'--no-ignore', '^\\.git$',
|
|
117
|
-
'--absolute-path',
|
|
118
|
-
'--threads', str(max_workers),
|
|
119
|
-
path_str
|
|
120
|
-
],
|
|
140
|
+
wt_proc: subprocess.CompletedProcess[str] = subprocess.run(
|
|
141
|
+
["git", "-C", str(repo_path), "worktree", "list", "--porcelain"],
|
|
121
142
|
capture_output=True,
|
|
122
143
|
text=True,
|
|
123
|
-
check=
|
|
144
|
+
check=True,
|
|
124
145
|
)
|
|
125
|
-
except FileNotFoundError as err:
|
|
126
|
-
print(f"Error: {err}", file=sys.stderr)
|
|
127
|
-
sys.exit(1)
|
|
128
146
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
Path(git_dir).parent
|
|
133
|
-
for git_dir in proc.stdout.splitlines()
|
|
134
|
-
}
|
|
147
|
+
current_wt: Optional[Path] = None
|
|
148
|
+
is_bare: bool = False
|
|
135
149
|
|
|
150
|
+
for line in wt_proc.stdout.splitlines():
|
|
151
|
+
line = line.strip()
|
|
136
152
|
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
# Git porcelain separates blocks with a blank line
|
|
154
|
+
if not line:
|
|
155
|
+
if current_wt and not is_bare \
|
|
156
|
+
and current_wt != repo_path.resolve():
|
|
157
|
+
sub_repos.add(current_wt)
|
|
158
|
+
# Reset state for the next block
|
|
159
|
+
current_wt = None
|
|
160
|
+
is_bare = False
|
|
161
|
+
continue
|
|
139
162
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if
|
|
147
|
-
|
|
163
|
+
if line.startswith("worktree "):
|
|
164
|
+
current_wt = Path(line[9:]).resolve()
|
|
165
|
+
elif line == "bare":
|
|
166
|
+
is_bare = True
|
|
167
|
+
|
|
168
|
+
# Process the final block in case the output lacks a trailing newline
|
|
169
|
+
if current_wt and not is_bare and current_wt != repo_path.resolve():
|
|
170
|
+
sub_repos.add(current_wt)
|
|
171
|
+
|
|
172
|
+
except subprocess.CalledProcessError:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
# Discover submodules
|
|
176
|
+
try:
|
|
177
|
+
sm_proc: subprocess.CompletedProcess[str] = subprocess.run(
|
|
178
|
+
["git", "-C", str(repo_path),
|
|
179
|
+
"submodule", "foreach", "--quiet", "pwd"],
|
|
180
|
+
capture_output=True,
|
|
181
|
+
text=True,
|
|
182
|
+
check=True,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
for line in sm_proc.stdout.splitlines():
|
|
186
|
+
sm_path: Path = Path(line.strip()).resolve()
|
|
187
|
+
sub_repos.add(sm_path)
|
|
188
|
+
|
|
189
|
+
except subprocess.CalledProcessError:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
return sub_repos
|
|
148
193
|
|
|
149
194
|
|
|
150
195
|
def run_command_get_output(repo_path: Path, cmd_list: list[str],
|
|
@@ -242,24 +287,39 @@ def process_repo(repo_path: Path, exec_parallel_cmd: Optional[list[str]],
|
|
|
242
287
|
|
|
243
288
|
def discover_repos(directory: Path,
|
|
244
289
|
max_workers: int,
|
|
245
|
-
exclude_dirs: list[str]
|
|
246
|
-
|
|
290
|
+
exclude_dirs: list[str],
|
|
291
|
+
include_sub_repos: bool) -> set[Path]:
|
|
292
|
+
"""Discover Git repositories starting from directory and apply exclusions.
|
|
247
293
|
|
|
248
294
|
:param directory: The root directory for search.
|
|
249
295
|
:param max_workers: Maximum number of threads/workers.
|
|
250
296
|
:param exclude_dirs: Directories to exclude.
|
|
297
|
+
:param include_sub_repos: Whether to include sub-repositories.
|
|
251
298
|
:return: A set of repository root paths.
|
|
252
299
|
"""
|
|
253
300
|
repos: set[Path] = set()
|
|
301
|
+
worktrees: set[Path] = set()
|
|
254
302
|
|
|
255
303
|
# Discover all repositories
|
|
256
304
|
toplevel: Optional[Path] = git_toplevel(directory)
|
|
257
305
|
if toplevel:
|
|
258
306
|
repos = {toplevel}
|
|
259
|
-
elif shutil.which("fd"):
|
|
260
|
-
repos = git_fd_find_repo(directory, max_workers)
|
|
261
307
|
else:
|
|
262
|
-
repos = git_py_find_repo(directory)
|
|
308
|
+
repos, worktrees = git_py_find_repo(directory)
|
|
309
|
+
|
|
310
|
+
# Extract and append sub-repositories concurrently if requested
|
|
311
|
+
if include_sub_repos:
|
|
312
|
+
sub_repos: set[Path] = set()
|
|
313
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
314
|
+
futures: list[Future[set[Path]]] = [
|
|
315
|
+
executor.submit(get_sub_repos, repo)
|
|
316
|
+
for repo in repos | worktrees
|
|
317
|
+
]
|
|
318
|
+
for future in as_completed(futures):
|
|
319
|
+
result: set[Path] = future.result()
|
|
320
|
+
sub_repos.update(result)
|
|
321
|
+
|
|
322
|
+
repos.update(sub_repos)
|
|
263
323
|
|
|
264
324
|
# Filter out excluded directories
|
|
265
325
|
if not exclude_dirs:
|
|
@@ -303,7 +363,7 @@ def execute_parallel_tasks(
|
|
|
303
363
|
result: Optional[RepoContext] = future.result()
|
|
304
364
|
if result:
|
|
305
365
|
results.append(result)
|
|
306
|
-
except
|
|
366
|
+
except (OSError, ValueError) as exc:
|
|
307
367
|
print(f"Error processing repository {repo_path}: {exc}",
|
|
308
368
|
file=sys.stderr)
|
|
309
369
|
|
|
@@ -357,6 +417,25 @@ def parse_args() -> argparse.Namespace:
|
|
|
357
417
|
"([EXEC] and [EXEC-P]) that prefix execution output."),
|
|
358
418
|
default=False
|
|
359
419
|
)
|
|
420
|
+
parser.add_argument(
|
|
421
|
+
"-s", "--include-sub-repos",
|
|
422
|
+
action="store_true",
|
|
423
|
+
help="Include sub-repositories (e.g., Git worktrees and submodules)",
|
|
424
|
+
default=False
|
|
425
|
+
)
|
|
426
|
+
parser.add_argument(
|
|
427
|
+
"--print",
|
|
428
|
+
action="store_true",
|
|
429
|
+
help="Print the paths (only when no command is provided)",
|
|
430
|
+
default=False
|
|
431
|
+
)
|
|
432
|
+
parser.add_argument(
|
|
433
|
+
"--print0",
|
|
434
|
+
action="store_true",
|
|
435
|
+
help=("Separate the paths with a null character (only when no "
|
|
436
|
+
"command is provided)"),
|
|
437
|
+
default=False
|
|
438
|
+
)
|
|
360
439
|
parser.add_argument(
|
|
361
440
|
"exec_cmd",
|
|
362
441
|
type=str,
|
|
@@ -418,6 +497,11 @@ def main() -> None:
|
|
|
418
497
|
|
|
419
498
|
args: argparse.Namespace = parse_args()
|
|
420
499
|
|
|
500
|
+
# Check for empty action
|
|
501
|
+
if not args.exec_cmd and not args.print and not args.print0:
|
|
502
|
+
print("Nothing to do.", file=sys.stderr)
|
|
503
|
+
sys.exit(0)
|
|
504
|
+
|
|
421
505
|
# Verify that the execution command exists
|
|
422
506
|
if args.exec_cmd and not shutil.which(args.exec_cmd[0]):
|
|
423
507
|
print(
|
|
@@ -428,7 +512,8 @@ def main() -> None:
|
|
|
428
512
|
# Discover Repositories
|
|
429
513
|
repos: set[Path] = discover_repos(args.directory.absolute(),
|
|
430
514
|
args.max_workers,
|
|
431
|
-
args.exclude_dir
|
|
515
|
+
args.exclude_dir,
|
|
516
|
+
args.include_sub_repos)
|
|
432
517
|
|
|
433
518
|
# setup background command list if needed
|
|
434
519
|
exec_parallel_cmd: Optional[list[str]] = None
|
|
@@ -499,7 +584,10 @@ def main() -> None:
|
|
|
499
584
|
|
|
500
585
|
# If no commands were run, just list the repo
|
|
501
586
|
if not args.exec_cmd:
|
|
502
|
-
|
|
587
|
+
if args.print0:
|
|
588
|
+
print(repo_path, end="\0")
|
|
589
|
+
elif args.print:
|
|
590
|
+
print(repo_path)
|
|
503
591
|
|
|
504
592
|
# Final Error Summary
|
|
505
593
|
sys.exit(print_error_summary(execution_errors))
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-rexec
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Locate Git repositories and execute commands against them in parallel.
|
|
5
5
|
Author: James Cherti
|
|
6
|
-
License: GPL-3.0-or-later
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/jamescherti/git-rexec
|
|
8
8
|
Keywords: git,parallel,executor,automation
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
10
|
Classifier: Operating System :: OS Independent
|
|
12
11
|
Classifier: Environment :: Console
|
|
13
12
|
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
@@ -19,7 +18,7 @@ Requires-Dist: setproctitle; extra == "extras"
|
|
|
19
18
|
|
|
20
19
|
# git-rexec: Find Git Repositories and Execute Commands Against Them, either Sequentially or in Parallel
|
|
21
20
|
|
|
22
|
-
The [git-rexec](https://github.com/jamescherti/git-rexec/) command-line tool
|
|
21
|
+
The [git-rexec](https://github.com/jamescherti/git-rexec/) command-line tool recursively locates Git repositories within a directory and executes commands against them, either sequentially or in parallel.
|
|
23
22
|
|
|
24
23
|
Here are examples demonstrating how to use `git-rexec`:
|
|
25
24
|
- Execute `git status -s` across all discovered Git repositories (found by searching recursively under the current working directory) in parallel (`-p` or `--parallel`):
|
|
@@ -32,6 +31,12 @@ Here are examples demonstrating how to use `git-rexec`:
|
|
|
32
31
|
git-rexec -j 5 --parallel -- git fetch
|
|
33
32
|
```
|
|
34
33
|
|
|
34
|
+
|
|
35
|
+
- Include sub-repositories (e.g., Git worktrees and submodules) alongside standard Git repositories during discovery (`-s` or `--include-sub-repos`), and execute `git status` against them:
|
|
36
|
+
```bash
|
|
37
|
+
git-rexec -s -p -- git status
|
|
38
|
+
```
|
|
39
|
+
|
|
35
40
|
- Target a specific base directory (`~/projects`) using the `-C` flag to recursively discover repositories within it, while explicitly excluding a specific subfolder (`~/projects/archive`). This example executes `git status -s` in parallel for all discovered repositories except those within the excluded path:
|
|
36
41
|
```bash
|
|
37
42
|
git-rexec -C ~/projects --exclude-dir ~/projects/archive --parallel -- git status -s
|
|
@@ -42,15 +47,21 @@ Here are examples demonstrating how to use `git-rexec`:
|
|
|
42
47
|
git-rexec --if-exec 'sh -c "test -f README.md"' --parallel -- wc -l README.md
|
|
43
48
|
```
|
|
44
49
|
|
|
50
|
+
- Print the paths of all discovered Git repositories:
|
|
51
|
+
```bash
|
|
52
|
+
git-rexec --print
|
|
53
|
+
```
|
|
54
|
+
|
|
45
55
|
If this helps your workflow, please support the project by **⭐ starring git-rexec on GitHub** and sharing it on your website, blog, Mastodon, Reddit, X, LinkedIn, or other social media platforms to help more Git users discover its benefits.
|
|
46
56
|
|
|
47
57
|
## Features
|
|
48
58
|
|
|
49
59
|
- Recursively discover Git repositories starting from a specified root directory.
|
|
60
|
+
- Optional flag to include sub-repositories (e.g., Git worktrees and submodules) in the execution target list.
|
|
50
61
|
- Execute shell commands across multiple repositories in parallel using worker threads.
|
|
51
62
|
- Filter target repositories based on the exit code of a conditional check (`--if-exec`).
|
|
52
63
|
- Exclude specific directories from the search path.
|
|
53
|
-
-
|
|
64
|
+
- Export discovered repository paths for integration with other shell tools (using `--print` or `--print0`).
|
|
54
65
|
|
|
55
66
|
## Installation
|
|
56
67
|
|
|
@@ -67,7 +78,7 @@ sudo cp git-rexec /usr/local/bin/
|
|
|
67
78
|
Install the package directly from the Git repository using `pip`:
|
|
68
79
|
|
|
69
80
|
```bash
|
|
70
|
-
pip install --user git
|
|
81
|
+
pip install --user git-rexec
|
|
71
82
|
```
|
|
72
83
|
|
|
73
84
|
## Dependencies
|
|
@@ -75,7 +86,6 @@ pip install --user git+https://github.com/jamescherti/git-rexec
|
|
|
75
86
|
### System Dependencies
|
|
76
87
|
|
|
77
88
|
- `git`: Required for repository validation and execution.
|
|
78
|
-
- `fd` (Optional): Highly recommended for faster repository discovery.
|
|
79
89
|
|
|
80
90
|
### Python Dependencies (Optional)
|
|
81
91
|
|
|
@@ -86,14 +96,12 @@ You can install the optional Python dependencies via pip:
|
|
|
86
96
|
|
|
87
97
|
```bash
|
|
88
98
|
pip install colorama setproctitle
|
|
89
|
-
|
|
90
99
|
```
|
|
91
100
|
|
|
92
101
|
## Usage
|
|
93
102
|
|
|
94
103
|
```bash
|
|
95
104
|
git-rexec [OPTIONS] [exec_cmd ...]
|
|
96
|
-
|
|
97
105
|
```
|
|
98
106
|
|
|
99
107
|
*(Assuming the `git-rexec` script is executable and in your PATH.)*
|
|
@@ -109,6 +117,7 @@ git-rexec [OPTIONS] [exec_cmd ...]
|
|
|
109
117
|
- `-p, --parallel`: Execute the command in parallel using threads.
|
|
110
118
|
- `-i, --if-exec <command>`: Execute the main command only if this check command returns an exit code of `0`.
|
|
111
119
|
- `-j, --jobs <int>`: The maximum number of concurrent workers/processors to use for parallel execution. Defaults to the number of CPU cores available.
|
|
120
|
+
- `-s, --include-sub-repos`: Include sub-repositories (e.g., Git worktrees and submodules) in the search target list.
|
|
112
121
|
- `-h, --help`: Show the help message and exit.
|
|
113
122
|
- `-q, --quiet`: Quiet mode. Suppresses the informational tracking headers (`[EXEC]` and `[EXEC-P]`) that prefix execution output. In sequential mode, it hides the `[EXEC]` repository delimiter line entirely; in parallel mode (`-p`), it strips the yellow `[EXEC-P]` header track and removes the four-space indentation, printing only the raw, unindented stdout and stderr streams. This flag has no effect when no execution command is supplied, allowing discovered repository paths to print normally.
|
|
114
123
|
|
|
@@ -4,18 +4,17 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "git-rexec"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Locate Git repositories and execute commands against them in parallel."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
|
-
license =
|
|
11
|
+
license = "GPL-3.0-or-later"
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "James Cherti"}
|
|
14
14
|
]
|
|
15
15
|
keywords = ["git", "parallel", "executor", "automation"]
|
|
16
16
|
classifiers = [
|
|
17
17
|
"Programming Language :: Python :: 3",
|
|
18
|
-
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
19
18
|
"Operating System :: OS Independent",
|
|
20
19
|
"Environment :: Console",
|
|
21
20
|
"Topic :: Software Development :: Version Control :: Git"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|