pulli 0.2.1__tar.gz → 0.2.5__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.
- {pulli-0.2.1 → pulli-0.2.5}/.gitignore +6 -0
- {pulli-0.2.1 → pulli-0.2.5}/PKG-INFO +61 -18
- {pulli-0.2.1 → pulli-0.2.5}/README.md +60 -17
- pulli-0.2.5/scripts/release.sh +79 -0
- {pulli-0.2.1 → pulli-0.2.5}/src/pulli/__init__.py +1 -1
- {pulli-0.2.1 → pulli-0.2.5}/src/pulli/cli.py +205 -7
- {pulli-0.2.1 → pulli-0.2.5}/src/pulli/discovery.py +6 -0
- {pulli-0.2.1 → pulli-0.2.5}/src/pulli/pull.py +16 -6
- {pulli-0.2.1 → pulli-0.2.5}/src/pulli/status.py +9 -1
- {pulli-0.2.1 → pulli-0.2.5}/src/pulli/tree.py +34 -16
- pulli-0.2.5/src/pulli/update.py +148 -0
- {pulli-0.2.1 → pulli-0.2.5}/tests/test_pulli.py +281 -4
- {pulli-0.2.1 → pulli-0.2.5}/uv.lock +1 -2
- {pulli-0.2.1 → pulli-0.2.5}/LICENSE +0 -0
- {pulli-0.2.1 → pulli-0.2.5}/pyproject.toml +0 -0
- {pulli-0.2.1 → pulli-0.2.5}/src/pulli/spinner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pulli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Walk a directory tree, show git repos with status, and fast-forward the ones that are behind upstream.
|
|
5
5
|
Author: Stefan Waldherr
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,16 +22,34 @@ fast-forward the ones that are behind upstream.
|
|
|
22
22
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
|
+
### Permanently (on your PATH)
|
|
26
|
+
|
|
25
27
|
```bash
|
|
26
|
-
uv tool install
|
|
28
|
+
uv tool install pulli
|
|
27
29
|
# or
|
|
28
|
-
pipx install
|
|
30
|
+
pipx install pulli
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
Both put `pulli` on your PATH (`~/.local/bin/pulli`).
|
|
32
|
-
|
|
33
|
+
Both put `pulli` on your PATH (`~/.local/bin/pulli`). After that just run
|
|
34
|
+
`pulli`.
|
|
35
|
+
|
|
36
|
+
### Once, without installing (`uvx`)
|
|
33
37
|
|
|
34
38
|
```bash
|
|
39
|
+
uvx pulli
|
|
40
|
+
uvx pulli ~/code
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`uvx` fetches `pulli` from PyPI into a temporary cache and runs it
|
|
44
|
+
immediately — no permanent install, no PATH entry. Great for a one-off run
|
|
45
|
+
or to try it out. `uv run --from pulli pulli` does the same thing inside a
|
|
46
|
+
`uv run` context.
|
|
47
|
+
|
|
48
|
+
### From Git / a dev checkout
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv tool install git+https://github.com/devskale/pulli
|
|
52
|
+
# or, for a local checkout (development)
|
|
35
53
|
cd ~/code/pulli
|
|
36
54
|
uv tool install --force .
|
|
37
55
|
```
|
|
@@ -57,12 +75,20 @@ connectors).
|
|
|
57
75
|
| flag | effect |
|
|
58
76
|
| --- | --- |
|
|
59
77
|
| `--tree` | show the full directory tree instead of the flat repo list |
|
|
78
|
+
| `--behind` | only the repos that need a pull |
|
|
79
|
+
| `--attention` | only dirty / diverged / behind / broken repos — what needs you first |
|
|
80
|
+
| `--no-summary` | omit the one-line summary under the list |
|
|
60
81
|
| `--no-fetch` | don't fetch remotes before showing status |
|
|
61
82
|
| `--no-symlinks` | don't follow symlinks (default: follow, marked) |
|
|
62
83
|
| `--max-depth N` | limit recursion (default: 50) |
|
|
63
|
-
| `--no-color` | disable ANSI colours (also implied off a TTY) |
|
|
84
|
+
| `--no-color` | disable ANSI colours (also implied off a TTY, by `NO_COLOR`, or `TERM=dumb`) |
|
|
64
85
|
| `--json` | one JSON object per line, for scripts |
|
|
65
86
|
|
|
87
|
+
`pulli --examples` prints a cheat sheet of common invocations.
|
|
88
|
+
|
|
89
|
+
The list ends with a one-line summary (`56 repos · 16 behind · 1 diverged …`),
|
|
90
|
+
so a scan answers "how are my repos doing?" without counting lines.
|
|
91
|
+
|
|
66
92
|
Fetches run in parallel by default, so ahead/behind is current, and each is
|
|
67
93
|
bounded by a short timeout — if you're offline (on a train) unreachable
|
|
68
94
|
remotes are reported as `offline` and the tree still renders.
|
|
@@ -73,6 +99,20 @@ frozen. The spinner only appears on a real terminal; when output is piped
|
|
|
73
99
|
or captured (`pulli | less`, CI, scripts) it is a no-op and the output stays
|
|
74
100
|
byte-clean. `--no-color` disables the spinner's colouring too.
|
|
75
101
|
|
|
102
|
+
### `update`
|
|
103
|
+
|
|
104
|
+
Checks PyPI and upgrades pulli itself — the installer that owns the binary
|
|
105
|
+
(`uv tool` or `pipx`) decides the upgrade command.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pulli update # upgrade to the latest PyPI release
|
|
109
|
+
pulli update --check # show what would be updated, change nothing
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`pulli --version` also reports when a newer release exists. The check hits
|
|
113
|
+
PyPI at most once a day (cached in the platform cache dir), never touches
|
|
114
|
+
the output of normal runs, and is silent when offline.
|
|
115
|
+
|
|
76
116
|
### `pull`
|
|
77
117
|
|
|
78
118
|
| flag | effect |
|
|
@@ -92,9 +132,10 @@ corrupt work to make progress.
|
|
|
92
132
|
| repo state | action |
|
|
93
133
|
| --- | --- |
|
|
94
134
|
| behind upstream, clean | `git pull --ff-only` |
|
|
135
|
+
| behind upstream, **untracked files only** | `git pull --ff-only` — untracked files cannot conflict with a fast-forward, so they are kept and the repo is pulled (marked `untracked N` instead of `dirty N`) |
|
|
95
136
|
| up to date | nothing (silent) |
|
|
96
137
|
| ahead only | nothing — local commits need a *push*, not a pull |
|
|
97
|
-
| uncommitted changes | skipped,
|
|
138
|
+
| uncommitted changes (tracked) | skipped, with the way out: *commit or stash, then pulli pull again* |
|
|
98
139
|
| diverged (ahead **and** behind) | skipped — a fast-forward is impossible; merge or rebase is your call |
|
|
99
140
|
| merge / rebase / bisect in progress | skipped, reported — never touched |
|
|
100
141
|
| no upstream (detached HEAD, no remote) | nothing to pull |
|
|
@@ -126,7 +167,7 @@ Would pull 1 repo(s).
|
|
|
126
167
|
### Flat list (default)
|
|
127
168
|
|
|
128
169
|
```
|
|
129
|
-
clones/pi earendil-works/pi main ↓0 ↑0 ●
|
|
170
|
+
clones/pi earendil-works/pi main ↓0 ↑0 ●
|
|
130
171
|
clones/gogcli openclaw/gogcli main ↓0 ↑0 ◐ dirty 5
|
|
131
172
|
kontext.one devskale/kontext.one main ↓0 ↑0 ◐ dirty 5
|
|
132
173
|
```
|
|
@@ -139,13 +180,13 @@ sorted by path, so a run is reproducible.
|
|
|
139
180
|
```
|
|
140
181
|
~/code
|
|
141
182
|
├── clones/
|
|
142
|
-
│ ├── herdr ogulcancelik/herdr master ↓0 ↑0 ●
|
|
143
|
-
│ ├── pi earendil-works/pi main ↓33 ↑0 ●
|
|
183
|
+
│ ├── herdr ogulcancelik/herdr master ↓0 ↑0 ●
|
|
184
|
+
│ ├── pi earendil-works/pi main ↓33 ↑0 ●
|
|
144
185
|
│ └── gogcli openclaw/gogcli main ↓0 ↑0 ◐ dirty 5
|
|
145
186
|
├── handoffs -> code/skaleshare/handoffs (alias)
|
|
146
187
|
├── kontext.one devskale/kontext.one main ↓0 ↑0 ◐ dirty 5
|
|
147
|
-
│ ├── klark0 devskale/klark0 dev ↓0 ↑0 ●
|
|
148
|
-
│ └── python-utils (submodule) ↓0 ↑0 ●
|
|
188
|
+
│ ├── klark0 devskale/klark0 dev ↓0 ↑0 ●
|
|
189
|
+
│ └── python-utils (submodule) ↓0 ↑0 ●
|
|
149
190
|
└── backups/
|
|
150
191
|
└── model-proxy.git/ (bare repo — nothing to pull)
|
|
151
192
|
```
|
|
@@ -155,13 +196,13 @@ sorted by path, so a run is reproducible.
|
|
|
155
196
|
```
|
|
156
197
|
~/code
|
|
157
198
|
├── clones/
|
|
158
|
-
│ ├── herdr ogulcancelik/herdr master ↓0 ↑0 ●
|
|
159
|
-
│ ├── pi earendil-works/pi main ↓33 ↑0 ●
|
|
199
|
+
│ ├── herdr ogulcancelik/herdr master ↓0 ↑0 ●
|
|
200
|
+
│ ├── pi earendil-works/pi main ↓33 ↑0 ●
|
|
160
201
|
│ └── gogcli openclaw/gogcli main ↓0 ↑0 ◐ dirty 5
|
|
161
202
|
├── handoffs -> code/skaleshare/handoffs (alias)
|
|
162
203
|
├── kontext.one devskale/kontext.one main ↓0 ↑0 ◐ dirty 5
|
|
163
|
-
│ ├── klark0 devskale/klark0 dev ↓0 ↑0 ●
|
|
164
|
-
│ └── python-utils (submodule) ↓0 ↑0 ●
|
|
204
|
+
│ ├── klark0 devskale/klark0 dev ↓0 ↑0 ●
|
|
205
|
+
│ └── python-utils (submodule) ↓0 ↑0 ●
|
|
165
206
|
└── backups/
|
|
166
207
|
└── model-proxy.git/ (bare repo — nothing to pull)
|
|
167
208
|
```
|
|
@@ -169,8 +210,10 @@ sorted by path, so a run is reproducible.
|
|
|
169
210
|
- **`↓N ↑M`** — commits behind / ahead of upstream; `· ·` means there is
|
|
170
211
|
no upstream to compare against (a fresh `git init`, a detached HEAD, a
|
|
171
212
|
remote-less clone), which is not the same as "in sync"
|
|
172
|
-
- **`●` clean / `◐` dirty N / `✗` error** — `◐ offline` means the fetch
|
|
173
|
-
failed, so the numbers may be stale
|
|
213
|
+
- **`●` clean (default) / `◐` dirty N / `✗` error** — `◐ offline` means the fetch
|
|
214
|
+
failed, so the numbers may be stale. Untracked-only content renders as
|
|
215
|
+
`◐ untracked N` (dim), not dirty: git itself does not consider untracked
|
|
216
|
+
content dirty, and a pull cannot conflict with it
|
|
174
217
|
- **symlinks** are followed by default and marked; one whose target is also
|
|
175
218
|
reachable under its real name is shown as an `(alias)` and is never a
|
|
176
219
|
second pull target. `--no-symlinks` skips them
|
|
@@ -5,16 +5,34 @@ fast-forward the ones that are behind upstream.
|
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
8
|
+
### Permanently (on your PATH)
|
|
9
|
+
|
|
8
10
|
```bash
|
|
9
|
-
uv tool install
|
|
11
|
+
uv tool install pulli
|
|
10
12
|
# or
|
|
11
|
-
pipx install
|
|
13
|
+
pipx install pulli
|
|
12
14
|
```
|
|
13
15
|
|
|
14
|
-
Both put `pulli` on your PATH (`~/.local/bin/pulli`).
|
|
15
|
-
|
|
16
|
+
Both put `pulli` on your PATH (`~/.local/bin/pulli`). After that just run
|
|
17
|
+
`pulli`.
|
|
18
|
+
|
|
19
|
+
### Once, without installing (`uvx`)
|
|
16
20
|
|
|
17
21
|
```bash
|
|
22
|
+
uvx pulli
|
|
23
|
+
uvx pulli ~/code
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`uvx` fetches `pulli` from PyPI into a temporary cache and runs it
|
|
27
|
+
immediately — no permanent install, no PATH entry. Great for a one-off run
|
|
28
|
+
or to try it out. `uv run --from pulli pulli` does the same thing inside a
|
|
29
|
+
`uv run` context.
|
|
30
|
+
|
|
31
|
+
### From Git / a dev checkout
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv tool install git+https://github.com/devskale/pulli
|
|
35
|
+
# or, for a local checkout (development)
|
|
18
36
|
cd ~/code/pulli
|
|
19
37
|
uv tool install --force .
|
|
20
38
|
```
|
|
@@ -40,12 +58,20 @@ connectors).
|
|
|
40
58
|
| flag | effect |
|
|
41
59
|
| --- | --- |
|
|
42
60
|
| `--tree` | show the full directory tree instead of the flat repo list |
|
|
61
|
+
| `--behind` | only the repos that need a pull |
|
|
62
|
+
| `--attention` | only dirty / diverged / behind / broken repos — what needs you first |
|
|
63
|
+
| `--no-summary` | omit the one-line summary under the list |
|
|
43
64
|
| `--no-fetch` | don't fetch remotes before showing status |
|
|
44
65
|
| `--no-symlinks` | don't follow symlinks (default: follow, marked) |
|
|
45
66
|
| `--max-depth N` | limit recursion (default: 50) |
|
|
46
|
-
| `--no-color` | disable ANSI colours (also implied off a TTY) |
|
|
67
|
+
| `--no-color` | disable ANSI colours (also implied off a TTY, by `NO_COLOR`, or `TERM=dumb`) |
|
|
47
68
|
| `--json` | one JSON object per line, for scripts |
|
|
48
69
|
|
|
70
|
+
`pulli --examples` prints a cheat sheet of common invocations.
|
|
71
|
+
|
|
72
|
+
The list ends with a one-line summary (`56 repos · 16 behind · 1 diverged …`),
|
|
73
|
+
so a scan answers "how are my repos doing?" without counting lines.
|
|
74
|
+
|
|
49
75
|
Fetches run in parallel by default, so ahead/behind is current, and each is
|
|
50
76
|
bounded by a short timeout — if you're offline (on a train) unreachable
|
|
51
77
|
remotes are reported as `offline` and the tree still renders.
|
|
@@ -56,6 +82,20 @@ frozen. The spinner only appears on a real terminal; when output is piped
|
|
|
56
82
|
or captured (`pulli | less`, CI, scripts) it is a no-op and the output stays
|
|
57
83
|
byte-clean. `--no-color` disables the spinner's colouring too.
|
|
58
84
|
|
|
85
|
+
### `update`
|
|
86
|
+
|
|
87
|
+
Checks PyPI and upgrades pulli itself — the installer that owns the binary
|
|
88
|
+
(`uv tool` or `pipx`) decides the upgrade command.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pulli update # upgrade to the latest PyPI release
|
|
92
|
+
pulli update --check # show what would be updated, change nothing
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`pulli --version` also reports when a newer release exists. The check hits
|
|
96
|
+
PyPI at most once a day (cached in the platform cache dir), never touches
|
|
97
|
+
the output of normal runs, and is silent when offline.
|
|
98
|
+
|
|
59
99
|
### `pull`
|
|
60
100
|
|
|
61
101
|
| flag | effect |
|
|
@@ -75,9 +115,10 @@ corrupt work to make progress.
|
|
|
75
115
|
| repo state | action |
|
|
76
116
|
| --- | --- |
|
|
77
117
|
| behind upstream, clean | `git pull --ff-only` |
|
|
118
|
+
| behind upstream, **untracked files only** | `git pull --ff-only` — untracked files cannot conflict with a fast-forward, so they are kept and the repo is pulled (marked `untracked N` instead of `dirty N`) |
|
|
78
119
|
| up to date | nothing (silent) |
|
|
79
120
|
| ahead only | nothing — local commits need a *push*, not a pull |
|
|
80
|
-
| uncommitted changes | skipped,
|
|
121
|
+
| uncommitted changes (tracked) | skipped, with the way out: *commit or stash, then pulli pull again* |
|
|
81
122
|
| diverged (ahead **and** behind) | skipped — a fast-forward is impossible; merge or rebase is your call |
|
|
82
123
|
| merge / rebase / bisect in progress | skipped, reported — never touched |
|
|
83
124
|
| no upstream (detached HEAD, no remote) | nothing to pull |
|
|
@@ -109,7 +150,7 @@ Would pull 1 repo(s).
|
|
|
109
150
|
### Flat list (default)
|
|
110
151
|
|
|
111
152
|
```
|
|
112
|
-
clones/pi earendil-works/pi main ↓0 ↑0 ●
|
|
153
|
+
clones/pi earendil-works/pi main ↓0 ↑0 ●
|
|
113
154
|
clones/gogcli openclaw/gogcli main ↓0 ↑0 ◐ dirty 5
|
|
114
155
|
kontext.one devskale/kontext.one main ↓0 ↑0 ◐ dirty 5
|
|
115
156
|
```
|
|
@@ -122,13 +163,13 @@ sorted by path, so a run is reproducible.
|
|
|
122
163
|
```
|
|
123
164
|
~/code
|
|
124
165
|
├── clones/
|
|
125
|
-
│ ├── herdr ogulcancelik/herdr master ↓0 ↑0 ●
|
|
126
|
-
│ ├── pi earendil-works/pi main ↓33 ↑0 ●
|
|
166
|
+
│ ├── herdr ogulcancelik/herdr master ↓0 ↑0 ●
|
|
167
|
+
│ ├── pi earendil-works/pi main ↓33 ↑0 ●
|
|
127
168
|
│ └── gogcli openclaw/gogcli main ↓0 ↑0 ◐ dirty 5
|
|
128
169
|
├── handoffs -> code/skaleshare/handoffs (alias)
|
|
129
170
|
├── kontext.one devskale/kontext.one main ↓0 ↑0 ◐ dirty 5
|
|
130
|
-
│ ├── klark0 devskale/klark0 dev ↓0 ↑0 ●
|
|
131
|
-
│ └── python-utils (submodule) ↓0 ↑0 ●
|
|
171
|
+
│ ├── klark0 devskale/klark0 dev ↓0 ↑0 ●
|
|
172
|
+
│ └── python-utils (submodule) ↓0 ↑0 ●
|
|
132
173
|
└── backups/
|
|
133
174
|
└── model-proxy.git/ (bare repo — nothing to pull)
|
|
134
175
|
```
|
|
@@ -138,13 +179,13 @@ sorted by path, so a run is reproducible.
|
|
|
138
179
|
```
|
|
139
180
|
~/code
|
|
140
181
|
├── clones/
|
|
141
|
-
│ ├── herdr ogulcancelik/herdr master ↓0 ↑0 ●
|
|
142
|
-
│ ├── pi earendil-works/pi main ↓33 ↑0 ●
|
|
182
|
+
│ ├── herdr ogulcancelik/herdr master ↓0 ↑0 ●
|
|
183
|
+
│ ├── pi earendil-works/pi main ↓33 ↑0 ●
|
|
143
184
|
│ └── gogcli openclaw/gogcli main ↓0 ↑0 ◐ dirty 5
|
|
144
185
|
├── handoffs -> code/skaleshare/handoffs (alias)
|
|
145
186
|
├── kontext.one devskale/kontext.one main ↓0 ↑0 ◐ dirty 5
|
|
146
|
-
│ ├── klark0 devskale/klark0 dev ↓0 ↑0 ●
|
|
147
|
-
│ └── python-utils (submodule) ↓0 ↑0 ●
|
|
187
|
+
│ ├── klark0 devskale/klark0 dev ↓0 ↑0 ●
|
|
188
|
+
│ └── python-utils (submodule) ↓0 ↑0 ●
|
|
148
189
|
└── backups/
|
|
149
190
|
└── model-proxy.git/ (bare repo — nothing to pull)
|
|
150
191
|
```
|
|
@@ -152,8 +193,10 @@ sorted by path, so a run is reproducible.
|
|
|
152
193
|
- **`↓N ↑M`** — commits behind / ahead of upstream; `· ·` means there is
|
|
153
194
|
no upstream to compare against (a fresh `git init`, a detached HEAD, a
|
|
154
195
|
remote-less clone), which is not the same as "in sync"
|
|
155
|
-
- **`●` clean / `◐` dirty N / `✗` error** — `◐ offline` means the fetch
|
|
156
|
-
failed, so the numbers may be stale
|
|
196
|
+
- **`●` clean (default) / `◐` dirty N / `✗` error** — `◐ offline` means the fetch
|
|
197
|
+
failed, so the numbers may be stale. Untracked-only content renders as
|
|
198
|
+
`◐ untracked N` (dim), not dirty: git itself does not consider untracked
|
|
199
|
+
content dirty, and a pull cannot conflict with it
|
|
157
200
|
- **symlinks** are followed by default and marked; one whose target is also
|
|
158
201
|
reachable under its real name is shown as an `(alias)` and is never a
|
|
159
202
|
second pull target. `--no-symlinks` skips them
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Local release builder — zero GitHub Actions minutes.
|
|
3
|
+
# Gates: clean worktree → tests green → version bump → tag → build →
|
|
4
|
+
# (optional) publish to PyPI.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bash scripts/release.sh 0.2.2 # bump, tag, build
|
|
8
|
+
# bash scripts/release.sh 0.2.2 --publish # ... and upload to PyPI
|
|
9
|
+
#
|
|
10
|
+
# Publish needs a PyPI token: UV_PUBLISH_TOKEN in the env, or pass it
|
|
11
|
+
# when prompted. Everything else runs fully offline.
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
REPO="devskale/pulli"
|
|
15
|
+
VERSION="${1:?usage: release.sh <version> [--publish] (no leading v)}"
|
|
16
|
+
VERSION="${VERSION#v}"
|
|
17
|
+
TAG="v$VERSION"
|
|
18
|
+
PUBLISH="${2:-}"
|
|
19
|
+
|
|
20
|
+
cd "$(dirname "$0")/.."
|
|
21
|
+
|
|
22
|
+
echo "── Gate: clean worktree ──"
|
|
23
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
24
|
+
echo " worktree is dirty — commit or stash first:" >&2
|
|
25
|
+
git status --porcelain >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
echo "── Gate: on main, up to date with origin ──"
|
|
30
|
+
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
31
|
+
[ "$BRANCH" = "main" ] || { echo " not on main (is $BRANCH)" >&2; exit 1; }
|
|
32
|
+
git fetch -q origin main
|
|
33
|
+
[ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ] || {
|
|
34
|
+
echo " main is not in sync with origin/main — push/pull first" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
echo "── Gate: tag does not exist yet ──"
|
|
39
|
+
git rev-parse -q --verify "refs/tags/$TAG" >/dev/null && {
|
|
40
|
+
echo " $TAG already exists" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
echo "── Gate: tests green ──"
|
|
45
|
+
uv run --extra dev pytest
|
|
46
|
+
|
|
47
|
+
echo "── Bump version → $VERSION ──"
|
|
48
|
+
INIT="src/pulli/__init__.py"
|
|
49
|
+
sed -i '' "s/^__version__ = .*/__version__ = \"$VERSION\"/" "$INIT"
|
|
50
|
+
git add "$INIT" uv.lock
|
|
51
|
+
git commit -q -m "v$VERSION" -m "Released via scripts/release.sh (local build, zero CI minutes)."
|
|
52
|
+
git tag -a "$TAG" -m "v$VERSION"
|
|
53
|
+
|
|
54
|
+
echo "── Sanity: built wheel reports the right version ──"
|
|
55
|
+
rm -rf dist && mkdir -p dist
|
|
56
|
+
uv build -q
|
|
57
|
+
uv run --no-project --with "./dist/pulli-$VERSION-py3-none-any.whl" \
|
|
58
|
+
pulli --version | grep -Fx "pulli $VERSION" || {
|
|
59
|
+
echo " version mismatch in wheel" >&2
|
|
60
|
+
exit 1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
echo "── Push commit + tag ──"
|
|
64
|
+
git push origin main "refs/tags/$TAG"
|
|
65
|
+
|
|
66
|
+
if [ "$PUBLISH" = "--publish" ]; then
|
|
67
|
+
echo "── Publish to PyPI ──"
|
|
68
|
+
if [ -z "${UV_PUBLISH_TOKEN:-}" ]; then
|
|
69
|
+
echo " UV_PUBLISH_TOKEN not set — get one at pypi.org (account settings"
|
|
70
|
+
echo " → API tokens, scope: this project) and re-run with it set:"
|
|
71
|
+
echo " UV_PUBLISH_TOKEN=pypi-... bash scripts/release.sh $VERSION --publish"
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
uv publish "dist/pulli-$VERSION-py3-none-any.whl" "dist/pulli-$VERSION.tar.gz"
|
|
75
|
+
echo "── Done: https://pypi.org/project/pulli/$VERSION ──"
|
|
76
|
+
else
|
|
77
|
+
echo "── Built (not published). To publish: ──"
|
|
78
|
+
echo " UV_PUBLISH_TOKEN=pypi-... uv publish dist/pulli-$VERSION*"
|
|
79
|
+
fi
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""pulli — discover git repos under a root and report their status."""
|
|
2
|
-
__version__ = "0.2.
|
|
2
|
+
__version__ = "0.2.5"
|