git-p4son 0.2.10__tar.gz → 0.3.0__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_p4son-0.2.10 → git_p4son-0.3.0}/PKG-INFO +55 -28
- {git_p4son-0.2.10 → git_p4son-0.3.0}/README.md +54 -27
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/__init__.py +1 -1
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/alias.py +38 -29
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/changelist_store.py +36 -6
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/cli.py +13 -12
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/common.py +95 -41
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/complete.py +5 -0
- git_p4son-0.3.0/git_p4son/config.py +96 -0
- git_p4son-0.3.0/git_p4son/git.py +438 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/hooks.py +14 -5
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/init.py +16 -13
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/lib.py +67 -17
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/log.py +48 -4
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/new.py +4 -2
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/perforce.py +125 -17
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/review.py +6 -0
- git_p4son-0.3.0/git_p4son/state.py +56 -0
- git_p4son-0.3.0/git_p4son/sync.py +699 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son.egg-info/PKG-INFO +55 -28
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son.egg-info/SOURCES.txt +5 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/pyproject.toml +1 -1
- git_p4son-0.3.0/tests/test_alias.py +107 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_changelist_store.py +59 -1
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_cli.py +8 -1
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_common.py +101 -6
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_complete.py +40 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_config.py +47 -0
- git_p4son-0.3.0/tests/test_git.py +407 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_hooks.py +46 -3
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_init.py +38 -12
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_lib_changelist.py +82 -21
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_lib_edit.py +106 -4
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_lib_review.py +27 -11
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_list_changes.py +2 -1
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_log.py +32 -0
- git_p4son-0.3.0/tests/test_new.py +56 -0
- git_p4son-0.3.0/tests/test_perforce.py +255 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/tests/test_review.py +16 -1
- git_p4son-0.3.0/tests/test_state.py +64 -0
- git_p4son-0.3.0/tests/test_sync.py +1205 -0
- git_p4son-0.2.10/git_p4son/config.py +0 -42
- git_p4son-0.2.10/git_p4son/git.py +0 -219
- git_p4son-0.2.10/git_p4son/sync.py +0 -255
- git_p4son-0.2.10/tests/test_perforce.py +0 -130
- git_p4son-0.2.10/tests/test_sync.py +0 -486
- {git_p4son-0.2.10 → git_p4son-0.3.0}/LICENSE +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/__main__.py +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/completions/_git-p4son +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/completions/git-p4son.bash +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/completions/git-p4son.ps1 +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/list_changes.py +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son/update.py +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son.egg-info/dependency_links.txt +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son.egg-info/entry_points.txt +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/git_p4son.egg-info/top_level.txt +0 -0
- {git_p4son-0.2.10 → git_p4son-0.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-p4son
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Utility for keeping a Perforce workspace and local git repo in sync
|
|
5
5
|
Author-email: Andreas Andersson <andreas@neoboid.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -156,41 +156,37 @@ cloning.
|
|
|
156
156
|
|
|
157
157
|
These steps set up git-p4son in an existing Perforce workspace. You only need to do this once.
|
|
158
158
|
|
|
159
|
-
1. **
|
|
160
|
-
P4V to set the clobber flag, or run `p4 client` and change `noclobber` to `clobber` in the Options line.
|
|
161
|
-
See [Why clobber?](#why-clobber) for rationale.
|
|
162
|
-
|
|
163
|
-
2. **Set a git editor** if you don't have one already. The `review` command opens an interactive rebase in your
|
|
159
|
+
1. **Set a git editor** if you don't have one already. The `review` command opens an interactive rebase in your
|
|
164
160
|
editor. If you haven't configured one, set it with:
|
|
165
161
|
```sh
|
|
166
162
|
git config --global core.editor <editor> # e.g. vim, nano, code --wait
|
|
167
163
|
```
|
|
168
164
|
|
|
169
|
-
|
|
165
|
+
2. **Sync your workspace to a known changelist.** Pick a changelist to use as the starting point for your git
|
|
170
166
|
history:
|
|
171
167
|
```sh
|
|
172
168
|
p4 sync //...@12345
|
|
173
169
|
```
|
|
174
170
|
|
|
175
|
-
|
|
171
|
+
3. **Run `git p4son init`.** This can be anywhere inside your Perforce workspace - it doesn't have to be at the
|
|
176
172
|
root:
|
|
177
173
|
```sh
|
|
178
174
|
cd /path/to/your/workspace # or a subdirectory of it
|
|
179
175
|
git p4son init
|
|
180
176
|
```
|
|
181
|
-
The command verifies that you are inside a P4 workspace
|
|
177
|
+
The command verifies that you are inside a P4 workspace, prompts you to select a depot
|
|
182
178
|
root (entire workspace or current directory subtree), runs `git init`, sets up `.gitignore` (copying from
|
|
183
179
|
`.p4ignore` if available), and creates an initial commit.
|
|
184
180
|
|
|
185
|
-
|
|
181
|
+
4. **Review `.gitignore`.** Edit the file to ensure build artifacts and other unwanted files are excluded.
|
|
186
182
|
|
|
187
|
-
|
|
183
|
+
5. **Add and commit all files manually the first time**
|
|
188
184
|
```sh
|
|
189
185
|
git add .
|
|
190
186
|
git commit -m "Initial submit all files"
|
|
191
187
|
```
|
|
192
188
|
|
|
193
|
-
|
|
189
|
+
6. **Run `git p4son sync`** to get your first official sync commit
|
|
194
190
|
```sh
|
|
195
191
|
git p4son sync
|
|
196
192
|
```
|
|
@@ -199,19 +195,41 @@ These steps set up git-p4son in an existing Perforce workspace. You only need to
|
|
|
199
195
|
From here, branch off `main` for local development. See the [Usage Example](#usage-example) for a typical
|
|
200
196
|
workflow.
|
|
201
197
|
|
|
202
|
-
###
|
|
198
|
+
### How sync handles writable files
|
|
199
|
+
|
|
200
|
+
When you switch branches with `git checkout`, git removes the read-only flag on every file it writes. This means
|
|
201
|
+
that when you switch back to `main` for a sync, Perforce may report "Can't clobber writable file" errors. git-p4son
|
|
202
|
+
handles these automatically by classifying each writable file:
|
|
203
|
+
|
|
204
|
+
**writable-unchanged** - The file on disk is identical to the last sync (only the read-only flag was removed
|
|
205
|
+
by git). This is the common case after switching branches. git-p4son detects this by comparing git blob IDs against
|
|
206
|
+
the last sync commit that touched the file, restores the read-only flag, and lets the sync overwrite the file.
|
|
207
|
+
|
|
208
|
+
**writable-changed** - The file on disk differs from the last sync. This happens when you manually edit a file
|
|
209
|
+
on `main` and commit it to git without submitting to Perforce. git-p4son lets the sync overwrite the file and
|
|
210
|
+
includes it in the sync commit (so the commit reflects pure Perforce state), then three-way merges your local
|
|
211
|
+
changes on top of the new Perforce content - similar to a `git rebase`. If the merge is clean, the result appears
|
|
212
|
+
as an unstaged change. If there are conflicts, standard conflict markers are inserted for you to resolve.
|
|
203
213
|
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
**added both locally and in Perforce** - A file you committed to git is also being added by Perforce (p4 has never
|
|
215
|
+
synced it to your workspace). There is no common baseline, so git-p4son merges the two versions against an empty
|
|
216
|
+
base: identical content merges cleanly, otherwise the file gets a single conflict block containing both full
|
|
217
|
+
versions for you to resolve.
|
|
206
218
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
with `p4 sync -f //path/to/file`, or enable the `clobber` flag on your workspace so that Perforce silently overwrites
|
|
210
|
-
writable files.
|
|
219
|
+
**binary files** - Binary files that have local changes cannot be three-way merged. git-p4son restores your local
|
|
220
|
+
version to disk after the sync commit so it shows up in `git status` for you to handle manually.
|
|
211
221
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
**added locally, deleted upstream** - A file with local changes in git was deleted in Perforce. git-p4son lets the
|
|
223
|
+
delete stand and warns you - your version remains available in git history.
|
|
224
|
+
|
|
225
|
+
**deleted locally, added upstream** - A file you deleted from git was modified in Perforce. The Perforce version is
|
|
226
|
+
kept in the sync commit. You can delete it again if you want.
|
|
227
|
+
|
|
228
|
+
**ignored** - Files ignored by `.gitignore` are skipped with a warning. These are outside git's domain and should
|
|
229
|
+
be managed manually or via Perforce directly.
|
|
230
|
+
|
|
231
|
+
After syncing, git-p4son prints a summary of how each writable file was handled, and instructs you to review and
|
|
232
|
+
commit any files that need attention.
|
|
215
233
|
|
|
216
234
|
## Usage
|
|
217
235
|
|
|
@@ -241,7 +259,7 @@ Initialize a git repository inside a Perforce workspace:
|
|
|
241
259
|
git p4son init
|
|
242
260
|
```
|
|
243
261
|
|
|
244
|
-
This command checks preconditions (Perforce workspace
|
|
262
|
+
This command checks preconditions (Perforce workspace), configures the depot root, runs `git init`,
|
|
245
263
|
sets up `.gitignore`, and creates an initial commit.
|
|
246
264
|
|
|
247
265
|
The depot root determines which part of the Perforce workspace git-p4son syncs. You can choose to sync the entire
|
|
@@ -266,7 +284,7 @@ git p4son sync [changelist] [--force]
|
|
|
266
284
|
sync to the latest changelist affecting the workspace.
|
|
267
285
|
|
|
268
286
|
**Options:**
|
|
269
|
-
- `-f, --force`:
|
|
287
|
+
- `-f, --force`: Allow syncing to changelists older than the current one.
|
|
270
288
|
|
|
271
289
|
**Examples:**
|
|
272
290
|
```sh
|
|
@@ -317,7 +335,11 @@ git p4son new -m "New feature" --review -b main
|
|
|
317
335
|
|
|
318
336
|
### Update Command
|
|
319
337
|
|
|
320
|
-
Update an existing Perforce changelist description
|
|
338
|
+
Update an existing Perforce changelist description. Commits since the base branch replace their existing
|
|
339
|
+
entries in the enumerated commit list (matched by subject) and new ones are appended; entries outside the
|
|
340
|
+
range are kept and the list is renumbered. So `update -b main` rebuilds the whole list without duplicating
|
|
341
|
+
it, while `update -b HEAD~3` only refreshes the last three entries. By default also opens changed files for
|
|
342
|
+
edit.
|
|
321
343
|
|
|
322
344
|
```sh
|
|
323
345
|
git p4son update [changelist] [--base-branch BASE_BRANCH] [--dry-run] [--no-desc] [--no-edit] [--shelve]
|
|
@@ -464,16 +486,21 @@ git p4son alias delete myfeature
|
|
|
464
486
|
|
|
465
487
|
#### alias clean
|
|
466
488
|
|
|
467
|
-
|
|
489
|
+
Clean up changelist aliases:
|
|
468
490
|
|
|
469
491
|
```sh
|
|
470
492
|
git p4son alias clean
|
|
471
493
|
```
|
|
472
494
|
|
|
473
|
-
|
|
495
|
+
The command lists all aliases and asks how to proceed:
|
|
496
|
+
- `a` (all): Delete all aliases
|
|
497
|
+
- `i` (interactive): Review each alias one by one
|
|
498
|
+
- `q` (quit): Exit without deleting anything
|
|
499
|
+
|
|
500
|
+
In interactive mode, each alias is displayed in turn with a prompt:
|
|
474
501
|
- `y` (yes): Delete this alias
|
|
475
502
|
- `n` (no): Keep this alias
|
|
476
|
-
- `a` (all): Delete this and all remaining aliases
|
|
503
|
+
- `a` (all): Delete this and all remaining aliases (handy for keeping the first few and sweeping the rest)
|
|
477
504
|
- `q` (quit): Stop and keep remaining aliases
|
|
478
505
|
|
|
479
506
|
**Examples:**
|
|
@@ -133,41 +133,37 @@ cloning.
|
|
|
133
133
|
|
|
134
134
|
These steps set up git-p4son in an existing Perforce workspace. You only need to do this once.
|
|
135
135
|
|
|
136
|
-
1. **
|
|
137
|
-
P4V to set the clobber flag, or run `p4 client` and change `noclobber` to `clobber` in the Options line.
|
|
138
|
-
See [Why clobber?](#why-clobber) for rationale.
|
|
139
|
-
|
|
140
|
-
2. **Set a git editor** if you don't have one already. The `review` command opens an interactive rebase in your
|
|
136
|
+
1. **Set a git editor** if you don't have one already. The `review` command opens an interactive rebase in your
|
|
141
137
|
editor. If you haven't configured one, set it with:
|
|
142
138
|
```sh
|
|
143
139
|
git config --global core.editor <editor> # e.g. vim, nano, code --wait
|
|
144
140
|
```
|
|
145
141
|
|
|
146
|
-
|
|
142
|
+
2. **Sync your workspace to a known changelist.** Pick a changelist to use as the starting point for your git
|
|
147
143
|
history:
|
|
148
144
|
```sh
|
|
149
145
|
p4 sync //...@12345
|
|
150
146
|
```
|
|
151
147
|
|
|
152
|
-
|
|
148
|
+
3. **Run `git p4son init`.** This can be anywhere inside your Perforce workspace - it doesn't have to be at the
|
|
153
149
|
root:
|
|
154
150
|
```sh
|
|
155
151
|
cd /path/to/your/workspace # or a subdirectory of it
|
|
156
152
|
git p4son init
|
|
157
153
|
```
|
|
158
|
-
The command verifies that you are inside a P4 workspace
|
|
154
|
+
The command verifies that you are inside a P4 workspace, prompts you to select a depot
|
|
159
155
|
root (entire workspace or current directory subtree), runs `git init`, sets up `.gitignore` (copying from
|
|
160
156
|
`.p4ignore` if available), and creates an initial commit.
|
|
161
157
|
|
|
162
|
-
|
|
158
|
+
4. **Review `.gitignore`.** Edit the file to ensure build artifacts and other unwanted files are excluded.
|
|
163
159
|
|
|
164
|
-
|
|
160
|
+
5. **Add and commit all files manually the first time**
|
|
165
161
|
```sh
|
|
166
162
|
git add .
|
|
167
163
|
git commit -m "Initial submit all files"
|
|
168
164
|
```
|
|
169
165
|
|
|
170
|
-
|
|
166
|
+
6. **Run `git p4son sync`** to get your first official sync commit
|
|
171
167
|
```sh
|
|
172
168
|
git p4son sync
|
|
173
169
|
```
|
|
@@ -176,19 +172,41 @@ These steps set up git-p4son in an existing Perforce workspace. You only need to
|
|
|
176
172
|
From here, branch off `main` for local development. See the [Usage Example](#usage-example) for a typical
|
|
177
173
|
workflow.
|
|
178
174
|
|
|
179
|
-
###
|
|
175
|
+
### How sync handles writable files
|
|
176
|
+
|
|
177
|
+
When you switch branches with `git checkout`, git removes the read-only flag on every file it writes. This means
|
|
178
|
+
that when you switch back to `main` for a sync, Perforce may report "Can't clobber writable file" errors. git-p4son
|
|
179
|
+
handles these automatically by classifying each writable file:
|
|
180
|
+
|
|
181
|
+
**writable-unchanged** - The file on disk is identical to the last sync (only the read-only flag was removed
|
|
182
|
+
by git). This is the common case after switching branches. git-p4son detects this by comparing git blob IDs against
|
|
183
|
+
the last sync commit that touched the file, restores the read-only flag, and lets the sync overwrite the file.
|
|
184
|
+
|
|
185
|
+
**writable-changed** - The file on disk differs from the last sync. This happens when you manually edit a file
|
|
186
|
+
on `main` and commit it to git without submitting to Perforce. git-p4son lets the sync overwrite the file and
|
|
187
|
+
includes it in the sync commit (so the commit reflects pure Perforce state), then three-way merges your local
|
|
188
|
+
changes on top of the new Perforce content - similar to a `git rebase`. If the merge is clean, the result appears
|
|
189
|
+
as an unstaged change. If there are conflicts, standard conflict markers are inserted for you to resolve.
|
|
180
190
|
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
**added both locally and in Perforce** - A file you committed to git is also being added by Perforce (p4 has never
|
|
192
|
+
synced it to your workspace). There is no common baseline, so git-p4son merges the two versions against an empty
|
|
193
|
+
base: identical content merges cleanly, otherwise the file gets a single conflict block containing both full
|
|
194
|
+
versions for you to resolve.
|
|
183
195
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
with `p4 sync -f //path/to/file`, or enable the `clobber` flag on your workspace so that Perforce silently overwrites
|
|
187
|
-
writable files.
|
|
196
|
+
**binary files** - Binary files that have local changes cannot be three-way merged. git-p4son restores your local
|
|
197
|
+
version to disk after the sync commit so it shows up in `git status` for you to handle manually.
|
|
188
198
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
199
|
+
**added locally, deleted upstream** - A file with local changes in git was deleted in Perforce. git-p4son lets the
|
|
200
|
+
delete stand and warns you - your version remains available in git history.
|
|
201
|
+
|
|
202
|
+
**deleted locally, added upstream** - A file you deleted from git was modified in Perforce. The Perforce version is
|
|
203
|
+
kept in the sync commit. You can delete it again if you want.
|
|
204
|
+
|
|
205
|
+
**ignored** - Files ignored by `.gitignore` are skipped with a warning. These are outside git's domain and should
|
|
206
|
+
be managed manually or via Perforce directly.
|
|
207
|
+
|
|
208
|
+
After syncing, git-p4son prints a summary of how each writable file was handled, and instructs you to review and
|
|
209
|
+
commit any files that need attention.
|
|
192
210
|
|
|
193
211
|
## Usage
|
|
194
212
|
|
|
@@ -218,7 +236,7 @@ Initialize a git repository inside a Perforce workspace:
|
|
|
218
236
|
git p4son init
|
|
219
237
|
```
|
|
220
238
|
|
|
221
|
-
This command checks preconditions (Perforce workspace
|
|
239
|
+
This command checks preconditions (Perforce workspace), configures the depot root, runs `git init`,
|
|
222
240
|
sets up `.gitignore`, and creates an initial commit.
|
|
223
241
|
|
|
224
242
|
The depot root determines which part of the Perforce workspace git-p4son syncs. You can choose to sync the entire
|
|
@@ -243,7 +261,7 @@ git p4son sync [changelist] [--force]
|
|
|
243
261
|
sync to the latest changelist affecting the workspace.
|
|
244
262
|
|
|
245
263
|
**Options:**
|
|
246
|
-
- `-f, --force`:
|
|
264
|
+
- `-f, --force`: Allow syncing to changelists older than the current one.
|
|
247
265
|
|
|
248
266
|
**Examples:**
|
|
249
267
|
```sh
|
|
@@ -294,7 +312,11 @@ git p4son new -m "New feature" --review -b main
|
|
|
294
312
|
|
|
295
313
|
### Update Command
|
|
296
314
|
|
|
297
|
-
Update an existing Perforce changelist description
|
|
315
|
+
Update an existing Perforce changelist description. Commits since the base branch replace their existing
|
|
316
|
+
entries in the enumerated commit list (matched by subject) and new ones are appended; entries outside the
|
|
317
|
+
range are kept and the list is renumbered. So `update -b main` rebuilds the whole list without duplicating
|
|
318
|
+
it, while `update -b HEAD~3` only refreshes the last three entries. By default also opens changed files for
|
|
319
|
+
edit.
|
|
298
320
|
|
|
299
321
|
```sh
|
|
300
322
|
git p4son update [changelist] [--base-branch BASE_BRANCH] [--dry-run] [--no-desc] [--no-edit] [--shelve]
|
|
@@ -441,16 +463,21 @@ git p4son alias delete myfeature
|
|
|
441
463
|
|
|
442
464
|
#### alias clean
|
|
443
465
|
|
|
444
|
-
|
|
466
|
+
Clean up changelist aliases:
|
|
445
467
|
|
|
446
468
|
```sh
|
|
447
469
|
git p4son alias clean
|
|
448
470
|
```
|
|
449
471
|
|
|
450
|
-
|
|
472
|
+
The command lists all aliases and asks how to proceed:
|
|
473
|
+
- `a` (all): Delete all aliases
|
|
474
|
+
- `i` (interactive): Review each alias one by one
|
|
475
|
+
- `q` (quit): Exit without deleting anything
|
|
476
|
+
|
|
477
|
+
In interactive mode, each alias is displayed in turn with a prompt:
|
|
451
478
|
- `y` (yes): Delete this alias
|
|
452
479
|
- `n` (no): Keep this alias
|
|
453
|
-
- `a` (all): Delete this and all remaining aliases
|
|
480
|
+
- `a` (all): Delete this and all remaining aliases (handy for keeping the first few and sweeping the rest)
|
|
454
481
|
- `q` (quit): Stop and keep remaining aliases
|
|
455
482
|
|
|
456
483
|
**Examples:**
|
|
@@ -8,6 +8,7 @@ from .changelist_store import (
|
|
|
8
8
|
list_changelist_aliases,
|
|
9
9
|
delete_changelist_alias,
|
|
10
10
|
)
|
|
11
|
+
from .common import prompt_choice
|
|
11
12
|
from .log import log
|
|
12
13
|
|
|
13
14
|
|
|
@@ -54,43 +55,27 @@ def alias_delete_command(args: argparse.Namespace) -> int:
|
|
|
54
55
|
return 0
|
|
55
56
|
|
|
56
57
|
|
|
57
|
-
def
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
except EOFError:
|
|
63
|
-
print()
|
|
64
|
-
return None
|
|
65
|
-
|
|
66
|
-
if response in ('y', 'yes'):
|
|
67
|
-
return 'yes'
|
|
68
|
-
elif response in ('n', 'no'):
|
|
69
|
-
return 'no'
|
|
70
|
-
elif response in ('a', 'all'):
|
|
71
|
-
return 'all'
|
|
72
|
-
elif response in ('q', 'quit'):
|
|
73
|
-
return 'quit'
|
|
74
|
-
else:
|
|
75
|
-
print('Please enter y, n, a, or q')
|
|
58
|
+
def _clean_all(aliases: list[tuple[str, str]], workspace_dir: str) -> None:
|
|
59
|
+
"""Delete every alias without further prompting."""
|
|
60
|
+
for name, _changelist in aliases:
|
|
61
|
+
if delete_changelist_alias(name, workspace_dir):
|
|
62
|
+
log.success(f'Deleted "{name}"')
|
|
76
63
|
|
|
77
64
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
aliases = list_changelist_aliases(workspace_dir)
|
|
83
|
-
if not aliases:
|
|
84
|
-
log.success('No changelist aliases to clean')
|
|
85
|
-
return 0
|
|
65
|
+
def _clean_interactive(aliases: list[tuple[str, str]],
|
|
66
|
+
workspace_dir: str) -> None:
|
|
67
|
+
"""Review each alias in turn with yes/no/all/quit prompts.
|
|
86
68
|
|
|
69
|
+
The all option lets the user keep the first aliases and sweep the rest:
|
|
70
|
+
answer no until the last one worth keeping, then a deletes the
|
|
71
|
+
remainder without further prompts."""
|
|
87
72
|
response = None
|
|
88
73
|
for name, changelist in aliases:
|
|
89
74
|
log.heading(f'{name} -> CL {changelist}')
|
|
90
75
|
|
|
91
76
|
if response != 'all':
|
|
92
|
-
response =
|
|
93
|
-
'Delete? [
|
|
77
|
+
response = prompt_choice(
|
|
78
|
+
'Delete?', ['yes', 'no', 'all', 'quit'])
|
|
94
79
|
|
|
95
80
|
if response is None or response == 'quit':
|
|
96
81
|
log.info('Aborting')
|
|
@@ -102,6 +87,30 @@ def alias_clean_command(args: argparse.Namespace) -> int:
|
|
|
102
87
|
if delete_changelist_alias(name, workspace_dir):
|
|
103
88
|
log.success('Deleted')
|
|
104
89
|
|
|
90
|
+
|
|
91
|
+
def alias_clean_command(args: argparse.Namespace) -> int:
|
|
92
|
+
"""Execute the 'alias clean' command."""
|
|
93
|
+
workspace_dir = args.workspace_dir
|
|
94
|
+
|
|
95
|
+
aliases = list_changelist_aliases(workspace_dir)
|
|
96
|
+
if not aliases:
|
|
97
|
+
log.success('No changelist aliases to clean')
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
log.info(f'Found {len(aliases)}')
|
|
101
|
+
for name, changelist in aliases:
|
|
102
|
+
log.info(f'{name} -> {changelist}')
|
|
103
|
+
|
|
104
|
+
mode = prompt_choice('Delete?', ['all', 'interactive', 'quit'])
|
|
105
|
+
if mode is None or mode == 'quit':
|
|
106
|
+
log.info('Aborting')
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
if mode == 'all':
|
|
110
|
+
_clean_all(aliases, workspace_dir)
|
|
111
|
+
else:
|
|
112
|
+
_clean_interactive(aliases, workspace_dir)
|
|
113
|
+
|
|
105
114
|
return 0
|
|
106
115
|
|
|
107
116
|
|
|
@@ -19,6 +19,13 @@ RESERVED_KEYWORDS = frozenset({'last-synced', 'branch'})
|
|
|
19
19
|
# (Windows disallows trailing dots).
|
|
20
20
|
_ALIAS_NAME_RE = re.compile(r'^[A-Za-z0-9_-]([A-Za-z0-9._-]*[A-Za-z0-9_-])?$')
|
|
21
21
|
|
|
22
|
+
# Windows device names; opening such a path hits the device namespace
|
|
23
|
+
# instead of creating a file, on any drive and regardless of extension.
|
|
24
|
+
_WINDOWS_RESERVED_NAMES = frozenset(
|
|
25
|
+
{'con', 'prn', 'aux', 'nul'}
|
|
26
|
+
| {f'com{i}' for i in range(1, 10)}
|
|
27
|
+
| {f'lpt{i}' for i in range(1, 10)})
|
|
28
|
+
|
|
22
29
|
|
|
23
30
|
def validate_alias_name(name: str) -> str | None:
|
|
24
31
|
"""Return an error message if alias name is invalid, else None."""
|
|
@@ -30,6 +37,15 @@ def validate_alias_name(name: str) -> str | None:
|
|
|
30
37
|
return (
|
|
31
38
|
f'Invalid alias name "{name}": must contain only letters, digits, '
|
|
32
39
|
'hyphens, underscores, and dots, and must not start or end with a dot')
|
|
40
|
+
if name.isdigit():
|
|
41
|
+
# Digit strings are always interpreted as changelist numbers, so
|
|
42
|
+
# such an alias could be created but never referenced.
|
|
43
|
+
return (
|
|
44
|
+
f'Invalid alias name "{name}": an all-digit name would be '
|
|
45
|
+
'indistinguishable from a changelist number')
|
|
46
|
+
if name.split('.', 1)[0].lower() in _WINDOWS_RESERVED_NAMES:
|
|
47
|
+
return (
|
|
48
|
+
f'Invalid alias name "{name}": reserved device name on Windows')
|
|
33
49
|
return None
|
|
34
50
|
|
|
35
51
|
|
|
@@ -38,6 +54,18 @@ def _changelists_dir(workspace_dir: str) -> str:
|
|
|
38
54
|
return os.path.join(workspace_dir, CONFIG_DIR, 'changelists')
|
|
39
55
|
|
|
40
56
|
|
|
57
|
+
def _alias_path(name: str, workspace_dir: str) -> str | None:
|
|
58
|
+
"""Validate name and return its store path, or None if invalid.
|
|
59
|
+
|
|
60
|
+
Validating on every lookup, not just on save, keeps raw user input
|
|
61
|
+
(e.g. "../../somefile") from escaping the store directory."""
|
|
62
|
+
error = validate_alias_name(name)
|
|
63
|
+
if error:
|
|
64
|
+
log.error(error)
|
|
65
|
+
return None
|
|
66
|
+
return os.path.join(_changelists_dir(workspace_dir), name)
|
|
67
|
+
|
|
68
|
+
|
|
41
69
|
def alias_exists(name: str, workspace_dir: str) -> bool:
|
|
42
70
|
"""Check whether a changelist alias exists."""
|
|
43
71
|
alias_path = os.path.join(_changelists_dir(workspace_dir), name)
|
|
@@ -46,13 +74,11 @@ def alias_exists(name: str, workspace_dir: str) -> bool:
|
|
|
46
74
|
|
|
47
75
|
def save_changelist_alias(name: str, changelist: str, workspace_dir: str, force: bool = False) -> bool:
|
|
48
76
|
"""Save a changelist number under a named alias."""
|
|
49
|
-
|
|
50
|
-
if
|
|
51
|
-
log.error(error)
|
|
77
|
+
alias_path = _alias_path(name, workspace_dir)
|
|
78
|
+
if alias_path is None:
|
|
52
79
|
return False
|
|
53
80
|
|
|
54
81
|
changelists_dir = _changelists_dir(workspace_dir)
|
|
55
|
-
alias_path = os.path.join(changelists_dir, name)
|
|
56
82
|
|
|
57
83
|
if os.path.exists(alias_path) and not force:
|
|
58
84
|
log.error(
|
|
@@ -71,7 +97,9 @@ def save_changelist_alias(name: str, changelist: str, workspace_dir: str, force:
|
|
|
71
97
|
|
|
72
98
|
def load_changelist_alias(name: str, workspace_dir: str) -> str | None:
|
|
73
99
|
"""Load a changelist number from a named alias, or None if not found."""
|
|
74
|
-
alias_path =
|
|
100
|
+
alias_path = _alias_path(name, workspace_dir)
|
|
101
|
+
if alias_path is None:
|
|
102
|
+
return None
|
|
75
103
|
|
|
76
104
|
if not os.path.exists(alias_path):
|
|
77
105
|
log.error(f'No changelist alias found: {name}')
|
|
@@ -108,7 +136,9 @@ def list_changelist_aliases(workspace_dir: str) -> list[tuple[str, str]]:
|
|
|
108
136
|
|
|
109
137
|
def delete_changelist_alias(name: str, workspace_dir: str) -> bool:
|
|
110
138
|
"""Delete a changelist alias file."""
|
|
111
|
-
alias_path =
|
|
139
|
+
alias_path = _alias_path(name, workspace_dir)
|
|
140
|
+
if alias_path is None:
|
|
141
|
+
return False
|
|
112
142
|
|
|
113
143
|
if not os.path.exists(alias_path):
|
|
114
144
|
log.error(f'No changelist alias found: {name}')
|
|
@@ -66,7 +66,7 @@ Examples:
|
|
|
66
66
|
'init',
|
|
67
67
|
help='Initialize a git repository inside a Perforce workspace',
|
|
68
68
|
description='Set up a new git repository inside a Perforce workspace. '
|
|
69
|
-
'Checks preconditions (P4 workspace
|
|
69
|
+
'Checks preconditions (P4 workspace), initializes git, '
|
|
70
70
|
'sets up .gitignore, and creates an initial commit.'
|
|
71
71
|
)
|
|
72
72
|
|
|
@@ -86,10 +86,7 @@ Examples:
|
|
|
86
86
|
sync_parser.add_argument(
|
|
87
87
|
'-f', '--force',
|
|
88
88
|
action='store_true',
|
|
89
|
-
help='
|
|
90
|
-
'When clobber is not enabled on your workspace, p4 will fail to sync files that '
|
|
91
|
-
'are read-only. git removes the readonly flag on touched files. Also allows '
|
|
92
|
-
'syncing to changelists older than the current one.'
|
|
89
|
+
help='Allow syncing to changelists older than the current one.'
|
|
93
90
|
)
|
|
94
91
|
|
|
95
92
|
# New subcommand
|
|
@@ -149,6 +146,7 @@ Examples:
|
|
|
149
146
|
)
|
|
150
147
|
new_parser.add_argument(
|
|
151
148
|
'-s', '--sleep',
|
|
149
|
+
type=int,
|
|
152
150
|
help='Sleep for the specified number of seconds after the command is done'
|
|
153
151
|
)
|
|
154
152
|
|
|
@@ -156,9 +154,11 @@ Examples:
|
|
|
156
154
|
update_parser = subparsers.add_parser(
|
|
157
155
|
'update',
|
|
158
156
|
help='Update an existing changelist description and open files for edit',
|
|
159
|
-
description='Update an existing Perforce changelist description
|
|
160
|
-
'
|
|
161
|
-
'
|
|
157
|
+
description='Update an existing Perforce changelist description: '
|
|
158
|
+
'commits since the base branch replace their existing entries in '
|
|
159
|
+
'the enumerated commit list and new ones are appended; entries '
|
|
160
|
+
'outside the range are kept. By default also opens changed files '
|
|
161
|
+
'for edit.'
|
|
162
162
|
)
|
|
163
163
|
update_parser.add_argument(
|
|
164
164
|
'changelist',
|
|
@@ -194,6 +194,7 @@ Examples:
|
|
|
194
194
|
)
|
|
195
195
|
update_parser.add_argument(
|
|
196
196
|
'-s', '--sleep',
|
|
197
|
+
type=int,
|
|
197
198
|
help='Sleep for the specified number of seconds after the command is done'
|
|
198
199
|
)
|
|
199
200
|
|
|
@@ -268,9 +269,9 @@ Examples:
|
|
|
268
269
|
# alias clean
|
|
269
270
|
alias_subparsers.add_parser(
|
|
270
271
|
'clean',
|
|
271
|
-
help='
|
|
272
|
-
description='
|
|
273
|
-
'yes/no/all/quit prompts'
|
|
272
|
+
help='Clean up changelist aliases',
|
|
273
|
+
description='List all changelist aliases, then delete all of them '
|
|
274
|
+
'or review each one interactively with yes/no/all/quit prompts'
|
|
274
275
|
)
|
|
275
276
|
|
|
276
277
|
# Review subcommand
|
|
@@ -468,7 +469,7 @@ def main() -> int:
|
|
|
468
469
|
exit_code = run_command(args)
|
|
469
470
|
|
|
470
471
|
if exit_code == 0 and getattr(args, 'sleep', None) is not None:
|
|
471
|
-
seconds =
|
|
472
|
+
seconds = args.sleep
|
|
472
473
|
log.heading(f'Sleeping for {seconds} seconds')
|
|
473
474
|
time.sleep(seconds)
|
|
474
475
|
log.success('awake again')
|