git-p4son 0.3.3__tar.gz → 0.4.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.3.3 → git_p4son-0.4.0}/PKG-INFO +80 -4
- {git_p4son-0.3.3 → git_p4son-0.4.0}/README.md +79 -3
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/__init__.py +1 -1
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/cli.py +40 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/common.py +34 -2
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/complete.py +12 -9
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/config.py +0 -17
- git_p4son-0.4.0/git_p4son/depot.py +62 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/git.py +36 -19
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/init.py +50 -6
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/lib.py +77 -1
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/log.py +32 -4
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/new.py +20 -1
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/perforce.py +67 -8
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/sync.py +173 -103
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/sync_split.py +6 -4
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/update.py +20 -1
- git_p4son-0.4.0/git_p4son/writable.py +155 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son.egg-info/PKG-INFO +80 -4
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son.egg-info/SOURCES.txt +7 -1
- {git_p4son-0.3.3 → git_p4son-0.4.0}/pyproject.toml +1 -1
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_cli.py +16 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_common.py +48 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_complete.py +7 -1
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_config.py +0 -33
- git_p4son-0.4.0/tests/test_depot.py +42 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_git.py +54 -1
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_init.py +94 -0
- git_p4son-0.4.0/tests/test_lib_revert.py +251 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_lib_review.py +10 -20
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_log.py +60 -0
- git_p4son-0.4.0/tests/test_new.py +121 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_perforce.py +30 -1
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_sync.py +338 -49
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_sync_split.py +24 -6
- git_p4son-0.4.0/tests/test_update.py +85 -0
- git_p4son-0.4.0/tests/test_writable.py +239 -0
- git_p4son-0.3.3/tests/test_new.py +0 -56
- {git_p4son-0.3.3 → git_p4son-0.4.0}/LICENSE +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/__main__.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/alias.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/changelist_store.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/completions/_git-p4son +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/completions/git-p4son.bash +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/completions/git-p4son.ps1 +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/hooks.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/list_changes.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/review.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son/state.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son.egg-info/dependency_links.txt +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son.egg-info/entry_points.txt +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/git_p4son.egg-info/top_level.txt +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/setup.cfg +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_alias.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_changelist_store.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_hooks.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_lib_changelist.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_lib_edit.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_list_changes.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_review.py +0 -0
- {git_p4son-0.3.3 → git_p4son-0.4.0}/tests/test_state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-p4son
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -231,10 +231,21 @@ be managed manually or via Perforce directly.
|
|
|
231
231
|
After syncing, git-p4son prints a summary of how each writable file was handled, and instructs you to review and
|
|
232
232
|
commit any files that need attention.
|
|
233
233
|
|
|
234
|
+
**Workspaces with `allwrite`** - When your client spec has the `allwrite` option, every file is writable by design,
|
|
235
|
+
so the read-only flag carries no information about local changes. git-p4son classifies files exactly as above (the
|
|
236
|
+
verdict comes from git, not from the flag), but leaves the write bit alone on files it finds unchanged: with
|
|
237
|
+
`noclobber` Perforce compares digests before refusing to overwrite a writable file, and unchanged files pass that
|
|
238
|
+
check. Only files with local changes are made read-only for the duration of the sync, since those are the ones
|
|
239
|
+
Perforce would otherwise refuse to overwrite. The same digest check applies to git-ignored files: unchanged ones sync
|
|
240
|
+
normally, and after the sync git-p4son lists the ones Perforce skipped because they were modified locally.
|
|
241
|
+
|
|
242
|
+
**Writable mode** - With [writable mode](#writable-command) on, the files a sync touches are made writable again after
|
|
243
|
+
the sync if git tracks them. The sync itself works as described above, since the workspace is not `allwrite`.
|
|
244
|
+
|
|
234
245
|
## Usage
|
|
235
246
|
|
|
236
|
-
git-p4son provides
|
|
237
|
-
`alias`, and `completion`.
|
|
247
|
+
git-p4son provides ten commands: `init`, `sync`, `sync-split`, `new`, `update`, `review`, `list-changes`,
|
|
248
|
+
`alias`, `writable`, and `completion`.
|
|
238
249
|
|
|
239
250
|
To see help for any command, use `-h`:
|
|
240
251
|
|
|
@@ -276,6 +287,9 @@ The `.gitignore` is set up using this priority:
|
|
|
276
287
|
- If `.p4ignore` exists, it is copied to `.gitignore` as a starting point
|
|
277
288
|
- Otherwise, an empty `.gitignore` is created
|
|
278
289
|
|
|
290
|
+
`init` also asks whether to turn on [writable mode](#writable-command), defaulting to the current setting. It only
|
|
291
|
+
saves the answer; run `git p4son writable apply` once your files are committed to update their permissions.
|
|
292
|
+
|
|
279
293
|
### Sync Command
|
|
280
294
|
|
|
281
295
|
Sync local git repository with a Perforce workspace:
|
|
@@ -371,6 +385,10 @@ than the last synced changelist (use `git p4son sync --force` for that).
|
|
|
371
385
|
Create a new Perforce changelist and add changed files to it. Description will contain an enumerated list of git commits since the base branch.
|
|
372
386
|
Optionally creates a Swarm review.
|
|
373
387
|
|
|
388
|
+
Opening files requires a clean git workspace, untracked files included; `new` refuses to run otherwise. The check
|
|
389
|
+
is skipped with `--no-edit`. After opening files, unchanged ones are reverted (see
|
|
390
|
+
[Keeping the changelist in step with git](#keeping-the-changelist-in-step-with-git)).
|
|
391
|
+
|
|
374
392
|
```sh
|
|
375
393
|
git p4son new -m <message> [alias] [--base-branch BASE_BRANCH] [--force] [--dry-run] [--no-edit] [--no-alias]
|
|
376
394
|
[--shelve] [--review]
|
|
@@ -406,7 +424,10 @@ Update an existing Perforce changelist description. Commits since the base branc
|
|
|
406
424
|
entries in the enumerated commit list (matched by subject) and new ones are appended; entries outside the
|
|
407
425
|
range are kept and the list is renumbered. So `update -b main` rebuilds the whole list without duplicating
|
|
408
426
|
it, while `update -b HEAD~3` only refreshes the last three entries. By default also opens changed files for
|
|
409
|
-
edit
|
|
427
|
+
edit, and reverts files that are no longer part of the git change (see
|
|
428
|
+
[Keeping the changelist in step with git](#keeping-the-changelist-in-step-with-git)). Opening files requires a
|
|
429
|
+
clean git workspace, untracked files included; `update` refuses to run otherwise. The check is skipped with
|
|
430
|
+
`--no-edit`.
|
|
410
431
|
|
|
411
432
|
```sh
|
|
412
433
|
git p4son update [changelist] [--base-branch BASE_BRANCH] [--dry-run] [--no-desc] [--no-edit] [--shelve]
|
|
@@ -432,6 +453,27 @@ git p4son update 12345
|
|
|
432
453
|
git p4son update myalias -b main
|
|
433
454
|
```
|
|
434
455
|
|
|
456
|
+
### Keeping the changelist in step with git
|
|
457
|
+
|
|
458
|
+
`new` and `update` open every file changed since the base branch in the changelist. A file can later stop being
|
|
459
|
+
part of the change, for example when a later commit undoes an edit, or a file is added in one commit and deleted
|
|
460
|
+
in the next. Such files would otherwise stay opened as no-ops, so after opening files both commands revert them:
|
|
461
|
+
|
|
462
|
+
- Files opened for edit that are unchanged compared to the depot are reverted with `p4 revert -a`. Changed files
|
|
463
|
+
stay opened.
|
|
464
|
+
- Files opened for add that are missing from disk are reverted.
|
|
465
|
+
- Files opened for delete that git still has (deleted in one commit, restored in a later one) are reverted and
|
|
466
|
+
restored from git.
|
|
467
|
+
|
|
468
|
+
Only files git-p4son has authority over are touched: files tracked by git, and files opened for add that are
|
|
469
|
+
missing from disk. Anything else in the changelist, such as binaries that only exist in Perforce and were opened
|
|
470
|
+
by hand, is left alone even when unchanged. Files opened with other actions (`move/add`, `integrate`, ...) are
|
|
471
|
+
also left alone. This step is skipped with `--no-edit`.
|
|
472
|
+
|
|
473
|
+
Shelving (`--shelve`, `--review`) replaces the whole shelf with the changelist's opened files using
|
|
474
|
+
`p4 shelve -r -a leaveunchanged`, so the shelf never holds stale files, and unchanged files that are still opened
|
|
475
|
+
are left out of it.
|
|
476
|
+
|
|
435
477
|
### Review Command
|
|
436
478
|
|
|
437
479
|
Automate the interactive rebase workflow for creating Swarm reviews. This command generates a rebase todo with `exec` lines that run `git p4son new --review` on the first commit and `git p4son update --shelve` on each subsequent commit, then opens it in your editor for review before executing.
|
|
@@ -575,6 +617,40 @@ In interactive mode, each alias is displayed in turn with a prompt:
|
|
|
575
617
|
git p4son alias clean
|
|
576
618
|
```
|
|
577
619
|
|
|
620
|
+
### Writable Command
|
|
621
|
+
|
|
622
|
+
Writable mode keeps git-tracked files writable, so you can edit them without running `p4 edit` first. git-p4son
|
|
623
|
+
opens changed files in Perforce itself when you run `new`, `update` or `review`. Git-ignored files, such as content
|
|
624
|
+
and other Perforce-only files, stay read-only and are checked out through Perforce as usual.
|
|
625
|
+
|
|
626
|
+
```sh
|
|
627
|
+
git p4son writable # Show whether writable mode is on
|
|
628
|
+
git p4son writable enable # Turn writable mode on, then apply it
|
|
629
|
+
git p4son writable disable # Turn writable mode off, then apply it
|
|
630
|
+
git p4son writable apply # Make git-tracked files match the current mode
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
`apply` makes every git-tracked file writable when the mode is on. When it's off, it makes them read-only, except
|
|
634
|
+
files opened in Perforce in any changelist. On a workspace with the `allwrite` option, making files read-only is
|
|
635
|
+
skipped, since the client spec wants every file writable. Git-ignored files are never changed.
|
|
636
|
+
|
|
637
|
+
The setting is stored in `.git-p4son/config.toml`:
|
|
638
|
+
|
|
639
|
+
```toml
|
|
640
|
+
[core]
|
|
641
|
+
writable = true
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
With the mode on, `sync` makes the files it synced writable again if git tracks them, since Perforce writes synced
|
|
645
|
+
files read-only. Files made read-only outside git-p4son, for example by submitting from P4V, `p4 revert`, or "Get
|
|
646
|
+
Latest" in P4V, stay read-only until a later sync touches them. Run `git p4son writable apply` to fix them right
|
|
647
|
+
away.
|
|
648
|
+
|
|
649
|
+
This replaces the `post-sync` hook some users run to make every tracked file writable after a sync.
|
|
650
|
+
|
|
651
|
+
Turning the mode off also makes tracked files whose Perforce type carries the `+w` (always writable) modifier
|
|
652
|
+
read-only. The next sync of such a file makes it writable again.
|
|
653
|
+
|
|
578
654
|
### Completion Command
|
|
579
655
|
|
|
580
656
|
Print the path to a shell completion script:
|
|
@@ -208,10 +208,21 @@ be managed manually or via Perforce directly.
|
|
|
208
208
|
After syncing, git-p4son prints a summary of how each writable file was handled, and instructs you to review and
|
|
209
209
|
commit any files that need attention.
|
|
210
210
|
|
|
211
|
+
**Workspaces with `allwrite`** - When your client spec has the `allwrite` option, every file is writable by design,
|
|
212
|
+
so the read-only flag carries no information about local changes. git-p4son classifies files exactly as above (the
|
|
213
|
+
verdict comes from git, not from the flag), but leaves the write bit alone on files it finds unchanged: with
|
|
214
|
+
`noclobber` Perforce compares digests before refusing to overwrite a writable file, and unchanged files pass that
|
|
215
|
+
check. Only files with local changes are made read-only for the duration of the sync, since those are the ones
|
|
216
|
+
Perforce would otherwise refuse to overwrite. The same digest check applies to git-ignored files: unchanged ones sync
|
|
217
|
+
normally, and after the sync git-p4son lists the ones Perforce skipped because they were modified locally.
|
|
218
|
+
|
|
219
|
+
**Writable mode** - With [writable mode](#writable-command) on, the files a sync touches are made writable again after
|
|
220
|
+
the sync if git tracks them. The sync itself works as described above, since the workspace is not `allwrite`.
|
|
221
|
+
|
|
211
222
|
## Usage
|
|
212
223
|
|
|
213
|
-
git-p4son provides
|
|
214
|
-
`alias`, and `completion`.
|
|
224
|
+
git-p4son provides ten commands: `init`, `sync`, `sync-split`, `new`, `update`, `review`, `list-changes`,
|
|
225
|
+
`alias`, `writable`, and `completion`.
|
|
215
226
|
|
|
216
227
|
To see help for any command, use `-h`:
|
|
217
228
|
|
|
@@ -253,6 +264,9 @@ The `.gitignore` is set up using this priority:
|
|
|
253
264
|
- If `.p4ignore` exists, it is copied to `.gitignore` as a starting point
|
|
254
265
|
- Otherwise, an empty `.gitignore` is created
|
|
255
266
|
|
|
267
|
+
`init` also asks whether to turn on [writable mode](#writable-command), defaulting to the current setting. It only
|
|
268
|
+
saves the answer; run `git p4son writable apply` once your files are committed to update their permissions.
|
|
269
|
+
|
|
256
270
|
### Sync Command
|
|
257
271
|
|
|
258
272
|
Sync local git repository with a Perforce workspace:
|
|
@@ -348,6 +362,10 @@ than the last synced changelist (use `git p4son sync --force` for that).
|
|
|
348
362
|
Create a new Perforce changelist and add changed files to it. Description will contain an enumerated list of git commits since the base branch.
|
|
349
363
|
Optionally creates a Swarm review.
|
|
350
364
|
|
|
365
|
+
Opening files requires a clean git workspace, untracked files included; `new` refuses to run otherwise. The check
|
|
366
|
+
is skipped with `--no-edit`. After opening files, unchanged ones are reverted (see
|
|
367
|
+
[Keeping the changelist in step with git](#keeping-the-changelist-in-step-with-git)).
|
|
368
|
+
|
|
351
369
|
```sh
|
|
352
370
|
git p4son new -m <message> [alias] [--base-branch BASE_BRANCH] [--force] [--dry-run] [--no-edit] [--no-alias]
|
|
353
371
|
[--shelve] [--review]
|
|
@@ -383,7 +401,10 @@ Update an existing Perforce changelist description. Commits since the base branc
|
|
|
383
401
|
entries in the enumerated commit list (matched by subject) and new ones are appended; entries outside the
|
|
384
402
|
range are kept and the list is renumbered. So `update -b main` rebuilds the whole list without duplicating
|
|
385
403
|
it, while `update -b HEAD~3` only refreshes the last three entries. By default also opens changed files for
|
|
386
|
-
edit
|
|
404
|
+
edit, and reverts files that are no longer part of the git change (see
|
|
405
|
+
[Keeping the changelist in step with git](#keeping-the-changelist-in-step-with-git)). Opening files requires a
|
|
406
|
+
clean git workspace, untracked files included; `update` refuses to run otherwise. The check is skipped with
|
|
407
|
+
`--no-edit`.
|
|
387
408
|
|
|
388
409
|
```sh
|
|
389
410
|
git p4son update [changelist] [--base-branch BASE_BRANCH] [--dry-run] [--no-desc] [--no-edit] [--shelve]
|
|
@@ -409,6 +430,27 @@ git p4son update 12345
|
|
|
409
430
|
git p4son update myalias -b main
|
|
410
431
|
```
|
|
411
432
|
|
|
433
|
+
### Keeping the changelist in step with git
|
|
434
|
+
|
|
435
|
+
`new` and `update` open every file changed since the base branch in the changelist. A file can later stop being
|
|
436
|
+
part of the change, for example when a later commit undoes an edit, or a file is added in one commit and deleted
|
|
437
|
+
in the next. Such files would otherwise stay opened as no-ops, so after opening files both commands revert them:
|
|
438
|
+
|
|
439
|
+
- Files opened for edit that are unchanged compared to the depot are reverted with `p4 revert -a`. Changed files
|
|
440
|
+
stay opened.
|
|
441
|
+
- Files opened for add that are missing from disk are reverted.
|
|
442
|
+
- Files opened for delete that git still has (deleted in one commit, restored in a later one) are reverted and
|
|
443
|
+
restored from git.
|
|
444
|
+
|
|
445
|
+
Only files git-p4son has authority over are touched: files tracked by git, and files opened for add that are
|
|
446
|
+
missing from disk. Anything else in the changelist, such as binaries that only exist in Perforce and were opened
|
|
447
|
+
by hand, is left alone even when unchanged. Files opened with other actions (`move/add`, `integrate`, ...) are
|
|
448
|
+
also left alone. This step is skipped with `--no-edit`.
|
|
449
|
+
|
|
450
|
+
Shelving (`--shelve`, `--review`) replaces the whole shelf with the changelist's opened files using
|
|
451
|
+
`p4 shelve -r -a leaveunchanged`, so the shelf never holds stale files, and unchanged files that are still opened
|
|
452
|
+
are left out of it.
|
|
453
|
+
|
|
412
454
|
### Review Command
|
|
413
455
|
|
|
414
456
|
Automate the interactive rebase workflow for creating Swarm reviews. This command generates a rebase todo with `exec` lines that run `git p4son new --review` on the first commit and `git p4son update --shelve` on each subsequent commit, then opens it in your editor for review before executing.
|
|
@@ -552,6 +594,40 @@ In interactive mode, each alias is displayed in turn with a prompt:
|
|
|
552
594
|
git p4son alias clean
|
|
553
595
|
```
|
|
554
596
|
|
|
597
|
+
### Writable Command
|
|
598
|
+
|
|
599
|
+
Writable mode keeps git-tracked files writable, so you can edit them without running `p4 edit` first. git-p4son
|
|
600
|
+
opens changed files in Perforce itself when you run `new`, `update` or `review`. Git-ignored files, such as content
|
|
601
|
+
and other Perforce-only files, stay read-only and are checked out through Perforce as usual.
|
|
602
|
+
|
|
603
|
+
```sh
|
|
604
|
+
git p4son writable # Show whether writable mode is on
|
|
605
|
+
git p4son writable enable # Turn writable mode on, then apply it
|
|
606
|
+
git p4son writable disable # Turn writable mode off, then apply it
|
|
607
|
+
git p4son writable apply # Make git-tracked files match the current mode
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
`apply` makes every git-tracked file writable when the mode is on. When it's off, it makes them read-only, except
|
|
611
|
+
files opened in Perforce in any changelist. On a workspace with the `allwrite` option, making files read-only is
|
|
612
|
+
skipped, since the client spec wants every file writable. Git-ignored files are never changed.
|
|
613
|
+
|
|
614
|
+
The setting is stored in `.git-p4son/config.toml`:
|
|
615
|
+
|
|
616
|
+
```toml
|
|
617
|
+
[core]
|
|
618
|
+
writable = true
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
With the mode on, `sync` makes the files it synced writable again if git tracks them, since Perforce writes synced
|
|
622
|
+
files read-only. Files made read-only outside git-p4son, for example by submitting from P4V, `p4 revert`, or "Get
|
|
623
|
+
Latest" in P4V, stay read-only until a later sync touches them. Run `git p4son writable apply` to fix them right
|
|
624
|
+
away.
|
|
625
|
+
|
|
626
|
+
This replaces the `post-sync` hook some users run to make every tracked file writable after a sync.
|
|
627
|
+
|
|
628
|
+
Turning the mode off also makes tracked files whose Perforce type carries the `+w` (always writable) modifier
|
|
629
|
+
read-only. The next sync of such a file makes it writable again.
|
|
630
|
+
|
|
555
631
|
### Completion Command
|
|
556
632
|
|
|
557
633
|
Print the path to a shell completion script:
|
|
@@ -21,6 +21,7 @@ from .common import CommandError, RunError, branch_to_alias
|
|
|
21
21
|
from .git import get_current_branch, get_head_subject, get_workspace_dir
|
|
22
22
|
from .log import log
|
|
23
23
|
from .complete import run_complete
|
|
24
|
+
from .writable import writable_command
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def create_parser() -> argparse.ArgumentParser:
|
|
@@ -46,6 +47,9 @@ Examples:
|
|
|
46
47
|
git-p4son update --shelve # Update changelist for current branch and re-shelve
|
|
47
48
|
git-p4son update 12345 # Update changelist 12345
|
|
48
49
|
git-p4son list-changes --base-branch main # List commit subjects since main branch
|
|
50
|
+
git-p4son writable # Show whether writable mode is on
|
|
51
|
+
git-p4son writable enable # Keep git-tracked files writable
|
|
52
|
+
git-p4son writable apply # Make tracked files match the writable mode
|
|
49
53
|
"""
|
|
50
54
|
)
|
|
51
55
|
|
|
@@ -352,6 +356,40 @@ Examples:
|
|
|
352
356
|
help='Print the generated rebase todo without executing'
|
|
353
357
|
)
|
|
354
358
|
|
|
359
|
+
# Writable subcommand
|
|
360
|
+
writable_parser = subparsers.add_parser(
|
|
361
|
+
'writable',
|
|
362
|
+
help='Show or apply writable mode for git-tracked files',
|
|
363
|
+
description='Writable mode keeps git-tracked files writable so they '
|
|
364
|
+
'can be edited without a manual p4 edit. Git-ignored files are left '
|
|
365
|
+
'to Perforce. Without an action, shows whether the mode is on.'
|
|
366
|
+
)
|
|
367
|
+
writable_subparsers = writable_parser.add_subparsers(
|
|
368
|
+
dest='writable_action',
|
|
369
|
+
help='Available writable actions',
|
|
370
|
+
metavar='ACTION'
|
|
371
|
+
)
|
|
372
|
+
writable_subparsers.add_parser(
|
|
373
|
+
'enable',
|
|
374
|
+
help='Turn writable mode on and make git-tracked files writable',
|
|
375
|
+
description='Turn writable mode on, then make every git-tracked file '
|
|
376
|
+
'writable. Sync keeps the files it syncs writable from then on.'
|
|
377
|
+
)
|
|
378
|
+
writable_subparsers.add_parser(
|
|
379
|
+
'disable',
|
|
380
|
+
help='Turn writable mode off and make git-tracked files read-only',
|
|
381
|
+
description='Turn writable mode off, then make git-tracked files '
|
|
382
|
+
'read-only, except files opened in Perforce.'
|
|
383
|
+
)
|
|
384
|
+
writable_subparsers.add_parser(
|
|
385
|
+
'apply',
|
|
386
|
+
help='Make git-tracked files match the writable mode',
|
|
387
|
+
description='With writable mode on, make every git-tracked file '
|
|
388
|
+
'writable. With it off, make them read-only, except files opened in '
|
|
389
|
+
'Perforce. Use it to fix files made read-only outside git-p4son, '
|
|
390
|
+
'for example by a submit.'
|
|
391
|
+
)
|
|
392
|
+
|
|
355
393
|
# Completion subcommand (prints shell completion script path)
|
|
356
394
|
completion_parser = subparsers.add_parser(
|
|
357
395
|
'completion',
|
|
@@ -477,6 +515,8 @@ def run_command(args: argparse.Namespace) -> int:
|
|
|
477
515
|
return alias_command(args)
|
|
478
516
|
elif args.command == 'review':
|
|
479
517
|
return review_command(args)
|
|
518
|
+
elif args.command == 'writable':
|
|
519
|
+
return writable_command(args)
|
|
480
520
|
elif args.command == '_sequence-editor':
|
|
481
521
|
return sequence_editor_command(args)
|
|
482
522
|
else:
|
|
@@ -11,6 +11,7 @@ import subprocess
|
|
|
11
11
|
import sys
|
|
12
12
|
import threading
|
|
13
13
|
import time
|
|
14
|
+
from contextlib import AbstractContextManager, nullcontext
|
|
14
15
|
from timeit import default_timer as timer
|
|
15
16
|
from datetime import timedelta
|
|
16
17
|
from typing import IO, Callable
|
|
@@ -127,6 +128,12 @@ class RunResult:
|
|
|
127
128
|
self.elapsed: timedelta | None = elapsed
|
|
128
129
|
|
|
129
130
|
|
|
131
|
+
# Commands that take a list of files pass it after a `--` separator. With
|
|
132
|
+
# thousands of files synced, printing every one buries the output, so a
|
|
133
|
+
# longer list is logged as a count. Verbose mode still prints it in full.
|
|
134
|
+
_MAX_LOGGED_PATHS = 3
|
|
135
|
+
|
|
136
|
+
|
|
130
137
|
def join_command_line(command: list[str]) -> str:
|
|
131
138
|
command_line = ''
|
|
132
139
|
for c in command:
|
|
@@ -137,6 +144,30 @@ def join_command_line(command: list[str]) -> str:
|
|
|
137
144
|
return command_line
|
|
138
145
|
|
|
139
146
|
|
|
147
|
+
def _command_line_for_log(command: list[str]) -> str:
|
|
148
|
+
"""The command line as printed, with a long path list summarized."""
|
|
149
|
+
if log.verbose_mode or '--' not in command:
|
|
150
|
+
return join_command_line(command)
|
|
151
|
+
separator = command.index('--')
|
|
152
|
+
paths = command[separator + 1:]
|
|
153
|
+
if len(paths) <= _MAX_LOGGED_PATHS:
|
|
154
|
+
return join_command_line(command)
|
|
155
|
+
return join_command_line(command[:separator + 1]) + f' <{len(paths)} paths>'
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def batched_command_log(prefix: list[str], path_count: int,
|
|
159
|
+
batch_count: int) -> AbstractContextManager[None]:
|
|
160
|
+
"""Log a command run over a path list in several batches as one line.
|
|
161
|
+
|
|
162
|
+
prefix is the command up to and including its `--` separator. A single
|
|
163
|
+
batch is logged as the command itself."""
|
|
164
|
+
if batch_count <= 1:
|
|
165
|
+
return nullcontext()
|
|
166
|
+
return log.command_batch(
|
|
167
|
+
join_command_line(prefix)
|
|
168
|
+
+ f' <{path_count} paths in {batch_count} batches>')
|
|
169
|
+
|
|
170
|
+
|
|
140
171
|
def run(command: list[str], cwd: str = '.', dry_run: bool = False,
|
|
141
172
|
input: str | None = None,
|
|
142
173
|
env: dict[str, str] | None = None,
|
|
@@ -159,7 +190,7 @@ def run(command: list[str], cwd: str = '.', dry_run: bool = False,
|
|
|
159
190
|
RunResult object with returncode, stdout, and stderr
|
|
160
191
|
"""
|
|
161
192
|
use_spinner = input is None and not dry_run
|
|
162
|
-
log.command(
|
|
193
|
+
log.command(_command_line_for_log(command),
|
|
163
194
|
truncate_for_spinner=use_spinner)
|
|
164
195
|
|
|
165
196
|
if dry_run:
|
|
@@ -242,7 +273,8 @@ def run_with_output(command: list[str], cwd: str = '.',
|
|
|
242
273
|
Returns:
|
|
243
274
|
RunResult object with returncode, stdout, and stderr
|
|
244
275
|
"""
|
|
245
|
-
log.command(
|
|
276
|
+
log.command(_command_line_for_log(command),
|
|
277
|
+
truncate_for_spinner=True)
|
|
246
278
|
log.start_spinner()
|
|
247
279
|
|
|
248
280
|
start_timestamp = timer()
|
|
@@ -14,6 +14,8 @@ from .git import get_current_branch, get_workspace_dir
|
|
|
14
14
|
from .log import log
|
|
15
15
|
|
|
16
16
|
_HIDDEN_COMMANDS = frozenset({'complete', 'completion', '_sequence-editor'})
|
|
17
|
+
# Commands whose first positional is a nested action (alias list, ...).
|
|
18
|
+
_COMMANDS_WITH_ACTIONS = frozenset({'alias', 'writable'})
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def _get_subparsers_action(parser):
|
|
@@ -125,15 +127,16 @@ def _complete_positional(command, subcommand, positional_count,
|
|
|
125
127
|
branch_candidates = _get_branch_candidates(prefix, workspace_dir)
|
|
126
128
|
return branch_candidates + _filter(aliases, prefix)
|
|
127
129
|
|
|
128
|
-
if command
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
if (command in _COMMANDS_WITH_ACTIONS and subcommand is None
|
|
131
|
+
and positional_count == 0):
|
|
132
|
+
nested = _get_subparsers_action(command_parser)
|
|
133
|
+
if nested and hasattr(nested, '_choices_actions'):
|
|
134
|
+
candidates = [(ca.dest, ca.help or '')
|
|
135
|
+
for ca in nested._choices_actions]
|
|
136
|
+
return _filter(candidates, prefix)
|
|
137
|
+
return []
|
|
136
138
|
|
|
139
|
+
if command == 'alias':
|
|
137
140
|
if subcommand == 'delete' and positional_count == 0:
|
|
138
141
|
branch_candidates = _get_branch_candidates(prefix, workspace_dir)
|
|
139
142
|
return branch_candidates + _filter(aliases, prefix)
|
|
@@ -193,7 +196,7 @@ def _complete(parser, words, workspace_dir=None):
|
|
|
193
196
|
current_parser = command_parser
|
|
194
197
|
continue
|
|
195
198
|
|
|
196
|
-
if command
|
|
199
|
+
if command in _COMMANDS_WITH_ACTIONS and subcommand is None:
|
|
197
200
|
nested = _get_subparsers_action(command_parser)
|
|
198
201
|
if nested and word in nested.choices:
|
|
199
202
|
subcommand = word
|
|
@@ -12,12 +12,6 @@ from . import CONFIG_DIR
|
|
|
12
12
|
|
|
13
13
|
_BARE_KEY_RE = re.compile(r'^[A-Za-z0-9_-]+$')
|
|
14
14
|
|
|
15
|
-
# Placeholder allowed in a stored depot root, substituted with the live
|
|
16
|
-
# Perforce client (workspace) name each time the root is used. Storing e.g.
|
|
17
|
-
# root = "//$(workspace)/Engine" keeps the config working after the workspace
|
|
18
|
-
# is renamed, at the cost of one client-name lookup per command.
|
|
19
|
-
WORKSPACE_PLACEHOLDER = '$(workspace)'
|
|
20
|
-
|
|
21
15
|
|
|
22
16
|
def config_path(workspace_dir: str) -> str:
|
|
23
17
|
"""Return the path to the config file."""
|
|
@@ -94,14 +88,3 @@ def _write_table(f, name: str, table: dict) -> None:
|
|
|
94
88
|
f.write('\n')
|
|
95
89
|
for key, subtable in subtables.items():
|
|
96
90
|
_write_table(f, f'{name}.{_format_key(key)}', subtable)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def get_depot_root(workspace_dir: str) -> str | None:
|
|
100
|
-
"""Get the depot root from config, or None if not configured."""
|
|
101
|
-
config = load_config(workspace_dir)
|
|
102
|
-
return config.get('depot', {}).get('root')
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def expand_depot_root(depot_root: str, workspace_name: str) -> str:
|
|
106
|
-
"""Substitute the live workspace name for the $(workspace) placeholder."""
|
|
107
|
-
return depot_root.replace(WORKSPACE_PLACEHOLDER, workspace_name)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Depot root resolution for git-p4son.
|
|
3
|
+
|
|
4
|
+
The depot root is the Perforce path git-p4son syncs, stored in
|
|
5
|
+
.git-p4son/config.toml. This module reads it, expands the $(workspace)
|
|
6
|
+
placeholder, and resolves it into the path Perforce commands run against.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from .config import load_config
|
|
12
|
+
from .log import log
|
|
13
|
+
from .perforce import P4ClientSpec, get_client_spec
|
|
14
|
+
|
|
15
|
+
# Placeholder allowed in a stored depot root, substituted with the live
|
|
16
|
+
# Perforce client (workspace) name each time the root is used. Storing e.g.
|
|
17
|
+
# root = "//$(workspace)/Engine" keeps the config working after the workspace
|
|
18
|
+
# is renamed, at the cost of one client-name lookup per command.
|
|
19
|
+
WORKSPACE_PLACEHOLDER = '$(workspace)'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_depot_root(workspace_dir: str) -> str | None:
|
|
23
|
+
"""Get the depot root from config, or None if not configured."""
|
|
24
|
+
config = load_config(workspace_dir)
|
|
25
|
+
return config.get('depot', {}).get('root')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def expand_depot_root(depot_root: str, workspace_name: str) -> str:
|
|
29
|
+
"""Substitute the live workspace name for the $(workspace) placeholder."""
|
|
30
|
+
return depot_root.replace(WORKSPACE_PLACEHOLDER, workspace_name)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ResolvedDepot:
|
|
35
|
+
"""The configured depot root with any placeholder expanded, plus the
|
|
36
|
+
client spec it was resolved against."""
|
|
37
|
+
depot_root: str
|
|
38
|
+
client_spec: P4ClientSpec | None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def resolve_depot_root(workspace_dir: str) -> ResolvedDepot | None:
|
|
42
|
+
"""Resolve the configured depot root, or None (with an error logged).
|
|
43
|
+
|
|
44
|
+
The client spec is queried once here: its name resolves a $(workspace)
|
|
45
|
+
placeholder in the depot root, and its line-ending/clobber options feed
|
|
46
|
+
the writable-file handling in sync.
|
|
47
|
+
"""
|
|
48
|
+
log.heading('Finding depot root')
|
|
49
|
+
depot_root = get_depot_root(workspace_dir)
|
|
50
|
+
if not depot_root:
|
|
51
|
+
log.error('No depot root configured. Run "git p4son init" first.')
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
client_spec = get_client_spec(workspace_dir)
|
|
55
|
+
if WORKSPACE_PLACEHOLDER in depot_root and not client_spec:
|
|
56
|
+
log.error('Cannot resolve $(workspace) in depot root: not inside a '
|
|
57
|
+
'Perforce workspace')
|
|
58
|
+
return None
|
|
59
|
+
if client_spec:
|
|
60
|
+
depot_root = expand_depot_root(depot_root, client_spec.name)
|
|
61
|
+
log.success(depot_root)
|
|
62
|
+
return ResolvedDepot(depot_root=depot_root, client_spec=client_spec)
|
|
@@ -10,6 +10,7 @@ import re
|
|
|
10
10
|
from .common import (
|
|
11
11
|
CommandError,
|
|
12
12
|
RunError,
|
|
13
|
+
batched_command_log,
|
|
13
14
|
normalize_workspace_path,
|
|
14
15
|
run,
|
|
15
16
|
run_with_output,
|
|
@@ -252,18 +253,28 @@ def get_tracked_files(filepaths: list[str], workspace_dir: str) -> set[str]:
|
|
|
252
253
|
tracked: set[str] = set()
|
|
253
254
|
chunks = _chunk_paths_by_length(
|
|
254
255
|
list(by_git_path), _PATHSPEC_LENGTH_BUDGET)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
for
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
# -z output is NUL-separated and verbatim; without it paths with
|
|
257
|
+
# non-ASCII characters are C-quoted and would never match.
|
|
258
|
+
command = ['git', 'ls-files', '-z', '--']
|
|
259
|
+
with batched_command_log(command, len(by_git_path), len(chunks)):
|
|
260
|
+
for chunk in chunks:
|
|
261
|
+
result = run(command + chunk, cwd=workspace_dir)
|
|
262
|
+
for line in result.stdout:
|
|
263
|
+
for git_path in line.split('\0'):
|
|
264
|
+
if git_path in by_git_path:
|
|
265
|
+
tracked.add(by_git_path[git_path])
|
|
264
266
|
return tracked
|
|
265
267
|
|
|
266
268
|
|
|
269
|
+
def list_tracked_files(workspace_dir: str) -> list[str]:
|
|
270
|
+
"""Return every file tracked by git, as repo-relative slash paths."""
|
|
271
|
+
# -z output is NUL-separated and verbatim, so non-ASCII paths come back
|
|
272
|
+
# as-is rather than C-quoted. run() splits its output on newlines, so
|
|
273
|
+
# they are rejoined first in case a path contains one.
|
|
274
|
+
result = run(['git', 'ls-files', '-z'], cwd=workspace_dir)
|
|
275
|
+
return [path for path in '\n'.join(result.stdout).split('\0') if path]
|
|
276
|
+
|
|
277
|
+
|
|
267
278
|
# --- file retrieval ---
|
|
268
279
|
|
|
269
280
|
def get_file_at_commit(filepath: str, commit: str,
|
|
@@ -361,14 +372,23 @@ def find_base_commits(filepaths: list[str], before_commit: str,
|
|
|
361
372
|
by_git_path = {fp.replace('\\', '/'): fp for fp in filepaths}
|
|
362
373
|
chunks = _chunk_paths_by_length(
|
|
363
374
|
list(by_git_path), _PATHSPEC_LENGTH_BUDGET)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
375
|
+
with batched_command_log(_find_base_commits_command(before_commit),
|
|
376
|
+
len(by_git_path), len(chunks)):
|
|
377
|
+
for chunk in chunks:
|
|
378
|
+
chunk_result = _find_base_commits_chunk(
|
|
379
|
+
chunk, before_commit, workspace_dir)
|
|
380
|
+
for git_path, sha in chunk_result.items():
|
|
381
|
+
result[by_git_path[git_path]] = sha
|
|
369
382
|
return result
|
|
370
383
|
|
|
371
384
|
|
|
385
|
+
def _find_base_commits_command(before_commit: str) -> list[str]:
|
|
386
|
+
"""The history walk behind find_base_commits, up to its `--`."""
|
|
387
|
+
return ['git', '-c', 'core.quotePath=false', 'log', '--no-renames',
|
|
388
|
+
'--name-status', '--pretty=format:%x01%H%x01%s',
|
|
389
|
+
before_commit, '--']
|
|
390
|
+
|
|
391
|
+
|
|
372
392
|
def _find_base_commits_chunk(git_paths: list[str], before_commit: str,
|
|
373
393
|
workspace_dir: str) -> dict[str, str | None]:
|
|
374
394
|
"""One newest-first history walk resolving baselines for git_paths.
|
|
@@ -378,11 +398,8 @@ def _find_base_commits_chunk(git_paths: list[str], before_commit: str,
|
|
|
378
398
|
no sync commit ever touched. core.quotePath is disabled so non-ASCII
|
|
379
399
|
paths in --name-status output match the input verbatim."""
|
|
380
400
|
result: dict[str, str | None] = dict.fromkeys(git_paths)
|
|
381
|
-
res = run(
|
|
382
|
-
|
|
383
|
-
'--name-status', '--pretty=format:%x01%H%x01%s',
|
|
384
|
-
before_commit, '--'] + git_paths,
|
|
385
|
-
cwd=workspace_dir, fail_on_returncode=False)
|
|
401
|
+
res = run(_find_base_commits_command(before_commit) + git_paths,
|
|
402
|
+
cwd=workspace_dir, fail_on_returncode=False)
|
|
386
403
|
if res.returncode != 0:
|
|
387
404
|
return result
|
|
388
405
|
|